feat(config): 重构镜像源配置管理
This commit is contained in:
@@ -203,7 +203,7 @@ export async function downloadPython(appRoot: string, mirror = 'ustc'): Promise<
|
|||||||
// Python 3.12.0嵌入式版本应该大约30MB,如果小于5MB可能是无效文件
|
// Python 3.12.0嵌入式版本应该大约30MB,如果小于5MB可能是无效文件
|
||||||
if (stats.size < 5 * 1024 * 1024) { // 5MB
|
if (stats.size < 5 * 1024 * 1024) { // 5MB
|
||||||
fs.unlinkSync(zipPath) // 删除无效文件
|
fs.unlinkSync(zipPath) // 删除无效文件
|
||||||
throw new Error(`Python下载文件大小异常: ${stats.size} bytes (${(stats.size / 1024).toFixed(2)} KB),可能是镜像站返回的错误页面或无效文件`)
|
throw new Error(`Python下载文件大小异常: ${stats.size} bytes (${(stats.size / 1024).toFixed(2)} KB),可能是镜像站返回的错误页面或无效文件。请选择一个其他可用镜像源进行下载!`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
|
|||||||
100
frontend/src/api/mirrors.ts
Normal file
100
frontend/src/api/mirrors.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* 镜像源 API 接口
|
||||||
|
* 用于从后端获取最新的镜像源配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { OpenAPI } from '@/api'
|
||||||
|
import type { MirrorConfig } from '@/config/mirrors'
|
||||||
|
|
||||||
|
export interface MirrorApiResponse {
|
||||||
|
git?: MirrorConfig[]
|
||||||
|
python?: MirrorConfig[]
|
||||||
|
pip?: MirrorConfig[]
|
||||||
|
apiEndpoints?: {
|
||||||
|
local?: string
|
||||||
|
production?: string
|
||||||
|
proxy?: string
|
||||||
|
}
|
||||||
|
downloadLinks?: {
|
||||||
|
[category: string]: {
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取镜像源配置
|
||||||
|
*/
|
||||||
|
export async function fetchMirrorConfig(): Promise<MirrorApiResponse> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${OpenAPI.BASE}/api/mirrors`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('获取镜像源配置失败,使用默认配置:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试镜像源连通性
|
||||||
|
*/
|
||||||
|
export async function testMirrorConnectivity(url: string, timeout: number = 5000): Promise<{
|
||||||
|
success: boolean
|
||||||
|
speed: number
|
||||||
|
error?: string
|
||||||
|
}> {
|
||||||
|
try {
|
||||||
|
const startTime = Date.now()
|
||||||
|
const controller = new AbortController()
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'HEAD',
|
||||||
|
signal: controller.signal,
|
||||||
|
cache: 'no-cache',
|
||||||
|
mode: 'no-cors' // 避免 CORS 问题
|
||||||
|
})
|
||||||
|
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
const speed = Date.now() - startTime
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
speed
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
speed: 9999,
|
||||||
|
error: error instanceof Error ? error.message : String(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量测试镜像源
|
||||||
|
*/
|
||||||
|
export async function batchTestMirrors(mirrors: MirrorConfig[]): Promise<MirrorConfig[]> {
|
||||||
|
const promises = mirrors.map(async (mirror) => {
|
||||||
|
const result = await testMirrorConnectivity(mirror.url)
|
||||||
|
return {
|
||||||
|
...mirror,
|
||||||
|
speed: result.speed
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const results = await Promise.all(promises)
|
||||||
|
|
||||||
|
// 按速度排序
|
||||||
|
return results.sort((a, b) => (a.speed || 9999) - (b.speed || 9999))
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { createComponentLogger } from '@/utils/logger'
|
import { createComponentLogger } from '@/utils/logger'
|
||||||
import { getConfig } from '@/utils/config'
|
import { getConfig } from '@/utils/config'
|
||||||
|
import { getMirrorUrl } from '@/config/mirrors'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
|
|
||||||
const logger = createComponentLogger('AutoMode')
|
const logger = createComponentLogger('AutoMode')
|
||||||
@@ -58,6 +59,9 @@ const progress = ref(0)
|
|||||||
const progressText = ref('')
|
const progressText = ref('')
|
||||||
const progressStatus = ref<'normal' | 'exception' | 'success'>('normal')
|
const progressStatus = ref<'normal' | 'exception' | 'success'>('normal')
|
||||||
|
|
||||||
|
// 状态:控制是否取消自动启动
|
||||||
|
const aborted = ref(false)
|
||||||
|
|
||||||
// 状态:控制弹窗显隐
|
// 状态:控制弹窗显隐
|
||||||
const forceEnterVisible = ref(false)
|
const forceEnterVisible = ref(false)
|
||||||
|
|
||||||
@@ -74,6 +78,7 @@ function handleForceEnterConfirm() {
|
|||||||
|
|
||||||
// 事件处理
|
// 事件处理
|
||||||
function handleSwitchToManual() {
|
function handleSwitchToManual() {
|
||||||
|
aborted.value = true // 设置中断
|
||||||
props.onSwitchToManual()
|
props.onSwitchToManual()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,12 +87,14 @@ async function startAutoProcess() {
|
|||||||
try {
|
try {
|
||||||
// 获取配置中保存的镜像源设置
|
// 获取配置中保存的镜像源设置
|
||||||
const config = await getConfig()
|
const config = await getConfig()
|
||||||
|
if (aborted.value) return
|
||||||
|
|
||||||
progressText.value = '检查Git仓库更新...'
|
progressText.value = '检查Git仓库更新...'
|
||||||
progress.value = 20
|
progress.value = 20
|
||||||
|
|
||||||
// 检查Git仓库是否有更新
|
// 检查Git仓库是否有更新
|
||||||
const hasUpdate = await checkGitUpdate()
|
const hasUpdate = await checkGitUpdate()
|
||||||
|
if (aborted.value) return
|
||||||
|
|
||||||
if (hasUpdate) {
|
if (hasUpdate) {
|
||||||
progressText.value = '发现更新,正在更新代码...'
|
progressText.value = '发现更新,正在更新代码...'
|
||||||
@@ -96,6 +103,7 @@ async function startAutoProcess() {
|
|||||||
// 使用配置中保存的Git镜像源
|
// 使用配置中保存的Git镜像源
|
||||||
const gitMirrorUrl = getGitMirrorUrl(config.selectedGitMirror)
|
const gitMirrorUrl = getGitMirrorUrl(config.selectedGitMirror)
|
||||||
const result = await window.electronAPI.updateBackend(gitMirrorUrl)
|
const result = await window.electronAPI.updateBackend(gitMirrorUrl)
|
||||||
|
if (aborted.value) return
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(`代码更新失败: ${result.error}`)
|
throw new Error(`代码更新失败: ${result.error}`)
|
||||||
}
|
}
|
||||||
@@ -108,6 +116,7 @@ async function startAutoProcess() {
|
|||||||
// 先尝试使用初始化时的镜像源
|
// 先尝试使用初始化时的镜像源
|
||||||
let pipMirror = config.selectedPipMirror || 'tsinghua'
|
let pipMirror = config.selectedPipMirror || 'tsinghua'
|
||||||
let pipResult = await window.electronAPI.installDependencies(pipMirror)
|
let pipResult = await window.electronAPI.installDependencies(pipMirror)
|
||||||
|
if (aborted.value) return
|
||||||
|
|
||||||
// 如果初始化时的镜像源不通,让用户重新选择
|
// 如果初始化时的镜像源不通,让用户重新选择
|
||||||
if (!pipResult.success) {
|
if (!pipResult.success) {
|
||||||
@@ -127,6 +136,7 @@ async function startAutoProcess() {
|
|||||||
progressText.value = '启动后端服务...'
|
progressText.value = '启动后端服务...'
|
||||||
progress.value = 80
|
progress.value = 80
|
||||||
await startBackendService()
|
await startBackendService()
|
||||||
|
if (aborted.value) return
|
||||||
|
|
||||||
progressText.value = '启动完成!'
|
progressText.value = '启动完成!'
|
||||||
progress.value = 100
|
progress.value = 100
|
||||||
@@ -167,16 +177,7 @@ async function checkGitUpdate(): Promise<boolean> {
|
|||||||
|
|
||||||
// 根据镜像源key获取对应的URL
|
// 根据镜像源key获取对应的URL
|
||||||
function getGitMirrorUrl(mirrorKey: string): string {
|
function getGitMirrorUrl(mirrorKey: string): string {
|
||||||
const mirrors = {
|
return getMirrorUrl('git', mirrorKey)
|
||||||
github: 'https://github.com/DLmaster361/AUTO_MAA.git',
|
|
||||||
ghfast: 'https://ghfast.top/https://github.com/DLmaster361/AUTO_MAA.git',
|
|
||||||
ghproxy_cloudflare: 'https://gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git.git',
|
|
||||||
ghproxy_hongkong: 'https://hk.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git.git',
|
|
||||||
ghproxy_fastly: 'https://cdn.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git.git',
|
|
||||||
ghproxy_edgeone: 'https://edgeone.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git.git'
|
|
||||||
}
|
|
||||||
|
|
||||||
return mirrors[mirrorKey as keyof typeof mirrors] || mirrors.github
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动后端服务
|
// 启动后端服务
|
||||||
@@ -189,6 +190,7 @@ async function startBackendService() {
|
|||||||
|
|
||||||
// 组件挂载时开始自动流程
|
// 组件挂载时开始自动流程
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
aborted.value = false
|
||||||
startAutoProcess()
|
startAutoProcess()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -50,14 +50,9 @@ defineProps<{
|
|||||||
backendExists: boolean
|
backendExists: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const gitMirrors = ref<Mirror[]>([
|
import { GIT_MIRRORS } from '@/config/mirrors'
|
||||||
{ key: 'github', name: 'GitHub 官方', url: 'https://github.com/DLmaster361/AUTO_MAA.git', speed: null },
|
|
||||||
{ key: 'ghfast', name: 'ghfast 镜像', url: 'https://ghfast.top/https://github.com/DLmaster361/AUTO_MAA.git', speed: null },
|
const gitMirrors = ref<Mirror[]>(GIT_MIRRORS)
|
||||||
{ key: 'ghproxy_cloudflare', name: 'gh-proxy Cloudflare 全球加速', url: 'https://gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git.git', speed: null },
|
|
||||||
{ key: 'ghproxy_hongkong', name: 'gh-proxy 香港节点(大陆优化)', url: 'https://hk.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git.git', speed: null },
|
|
||||||
{ key: 'ghproxy_fastly', name: 'gh-proxy Fastly CDN', url: 'https://cdn.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git.git', speed: null },
|
|
||||||
{ key: 'ghproxy_edgeone', name: 'gh-proxy EdgeOne 全球加速', url: 'https://edgeone.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git.git', speed: null }
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
const selectedGitMirror = ref('github')
|
const selectedGitMirror = ref('github')
|
||||||
|
|||||||
@@ -46,12 +46,9 @@ interface Mirror {
|
|||||||
speed: number | null
|
speed: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const pipMirrors = ref<Mirror[]>([
|
import { PIP_MIRRORS } from '@/config/mirrors'
|
||||||
{ key: 'official', name: 'PyPI 官方', url: 'https://pypi.org/simple/', speed: null },
|
|
||||||
{ key: 'tsinghua', name: '清华大学', url: 'https://pypi.tuna.tsinghua.edu.cn/simple/', speed: null },
|
const pipMirrors = ref<Mirror[]>(PIP_MIRRORS)
|
||||||
{ key: 'aliyun', name: '阿里云', url: 'https://mirrors.aliyun.com/pypi/simple/', speed: null },
|
|
||||||
{ key: 'ustc', name: '中科大', url: 'https://pypi.mirrors.ustc.edu.cn/simple/', speed: null },
|
|
||||||
])
|
|
||||||
|
|
||||||
const selectedPipMirror = ref('tsinghua')
|
const selectedPipMirror = ref('tsinghua')
|
||||||
const testingPipSpeed = ref(false)
|
const testingPipSpeed = ref(false)
|
||||||
|
|||||||
@@ -14,12 +14,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="already-installed">
|
<div v-else class="already-installed">
|
||||||
<a-result status="success" title="Git已成功安装,无需继续安装" />
|
<a-result status="success" title="Git已成功安装,无需继续安装" />
|
||||||
<div class="reinstall-section">
|
<!-- <div class="reinstall-section">-->
|
||||||
<a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">
|
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
|
||||||
{{ reinstalling ? '正在重新安装...' : '强制重新安装' }}
|
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
|
||||||
</a-button>
|
<!-- </a-button>-->
|
||||||
<p class="reinstall-note">点击此按钮将删除现有Git环境并重新安装</p>
|
<!-- <p class="reinstall-note">点击此按钮将删除现有Git环境并重新安装</p>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -28,15 +28,15 @@
|
|||||||
<a-step title="启动服务" description="启动后端服务" />
|
<a-step title="启动服务" description="启动后端服务" />
|
||||||
</a-steps>
|
</a-steps>
|
||||||
|
|
||||||
<!-- 全局进度条 -->
|
<!-- <!– 全局进度条 –>-->
|
||||||
<div v-if="isProcessing" class="global-progress">
|
<!-- <div v-if="isProcessing" class="global-progress">-->
|
||||||
<a-progress
|
<!-- <a-progress -->
|
||||||
:percent="globalProgress"
|
<!-- :percent="globalProgress" -->
|
||||||
:status="globalProgressStatus"
|
<!-- :status="globalProgressStatus"-->
|
||||||
:show-info="true"
|
<!-- :show-info="true"-->
|
||||||
/>
|
<!-- />-->
|
||||||
<div class="progress-text">{{ progressText }}</div>
|
<!-- <div class="progress-text">{{ progressText }}</div>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
|
|
||||||
<div class="step-content">
|
<div class="step-content">
|
||||||
<!-- 步骤 0: 主题设置 -->
|
<!-- 步骤 0: 主题设置 -->
|
||||||
@@ -94,20 +94,21 @@
|
|||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="errorMessage" class="error-message">
|
<!-- <div v-if="errorMessage" class="error-message">-->
|
||||||
<a-alert
|
<!-- <a-alert -->
|
||||||
:message="errorMessage"
|
<!-- :message="errorMessage" -->
|
||||||
type="error"
|
<!-- type="error" -->
|
||||||
show-icon
|
<!-- show-icon -->
|
||||||
closable
|
<!-- closable-->
|
||||||
@close="errorMessage = ''"
|
<!-- @close="errorMessage = ''"-->
|
||||||
/>
|
<!-- />-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
import { createComponentLogger } from '@/utils/logger'
|
import { createComponentLogger } from '@/utils/logger'
|
||||||
import { saveConfig } from '@/utils/config'
|
import { saveConfig } from '@/utils/config'
|
||||||
import ThemeStep from './ThemeStep.vue'
|
import ThemeStep from './ThemeStep.vue'
|
||||||
@@ -473,6 +474,15 @@ defineExpose({
|
|||||||
currentStep,
|
currentStep,
|
||||||
handleDownloadProgress
|
handleDownloadProgress
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听 errorMessage,一旦有内容就弹窗
|
||||||
|
watch(errorMessage, (val) => {
|
||||||
|
if (val) {
|
||||||
|
message.error(val)
|
||||||
|
// 弹窗后可选:自动清空 errorMessage
|
||||||
|
// errorMessage.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="already-installed">
|
<div v-else class="already-installed">
|
||||||
<a-result status="success" title="pip已成功安装,无需继续安装" />
|
<a-result status="success" title="pip已成功安装,无需继续安装" />
|
||||||
<div class="reinstall-section">
|
<!-- <div class="reinstall-section">-->
|
||||||
<a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">
|
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
|
||||||
{{ reinstalling ? '正在重新安装...' : '强制重新安装' }}
|
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
|
||||||
</a-button>
|
<!-- </a-button>-->
|
||||||
<p class="reinstall-note">点击此按钮将删除现有pip环境并重新安装</p>
|
<!-- <p class="reinstall-note">点击此按钮将删除现有pip环境并重新安装</p>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -29,12 +29,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="already-installed">
|
<div v-else class="already-installed">
|
||||||
<a-result status="success" title="Python已成功安装,无需继续安装" />
|
<a-result status="success" title="Python已成功安装,无需继续安装" />
|
||||||
<div class="reinstall-section">
|
<!-- <div class="reinstall-section">-->
|
||||||
<a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">
|
<!-- <a-button type="primary" danger @click="handleForceReinstall" :loading="reinstalling">-->
|
||||||
{{ reinstalling ? '正在重新安装...' : '强制重新安装' }}
|
<!-- {{ reinstalling ? '正在重新安装...' : '强制重新安装' }}-->
|
||||||
</a-button>
|
<!-- </a-button>-->
|
||||||
<p class="reinstall-note">点击此按钮将删除现有Python环境并重新安装</p>
|
<!-- <p class="reinstall-note">点击此按钮将删除现有Python环境并重新安装</p>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -54,13 +54,9 @@ const props = defineProps<{
|
|||||||
pythonInstalled: boolean
|
pythonInstalled: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const pythonMirrors = ref<Mirror[]>([
|
import { PYTHON_MIRRORS } from '@/config/mirrors'
|
||||||
{ key: 'official', name: 'Python 官方', url: 'https://www.python.org/ftp/python/3.13.0/', speed: null },
|
|
||||||
{ key: 'tsinghua', name: '清华 TUNA 镜像', url: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.13.0/', speed: null },
|
const pythonMirrors = ref<Mirror[]>(PYTHON_MIRRORS)
|
||||||
{ key: 'ustc', name: '中科大镜像', url: 'https://mirrors.ustc.edu.cn/python/3.13.0/', speed: null },
|
|
||||||
{ key: 'huawei', name: '华为云镜像', url: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.13.0/', speed: null },
|
|
||||||
{ key: 'aliyun', name: '阿里云镜像', url: 'https://mirrors.aliyun.com/python-release/windows/', speed: null }
|
|
||||||
])
|
|
||||||
|
|
||||||
const selectedPythonMirror = ref('tsinghua')
|
const selectedPythonMirror = ref('tsinghua')
|
||||||
const testingSpeed = ref(false)
|
const testingSpeed = ref(false)
|
||||||
|
|||||||
216
frontend/src/config/mirrors.ts
Normal file
216
frontend/src/config/mirrors.ts
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
/**
|
||||||
|
* 镜像源和下载链接配置文件
|
||||||
|
* 集中管理所有下载用到的链接,方便后续通过接口动态配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface MirrorConfig {
|
||||||
|
key: string
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
speed?: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MirrorCategory {
|
||||||
|
[key: string]: MirrorConfig[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Git 仓库镜像源配置
|
||||||
|
*/
|
||||||
|
export const GIT_MIRRORS: MirrorConfig[] = [
|
||||||
|
{
|
||||||
|
key: 'github',
|
||||||
|
name: 'GitHub 官方',
|
||||||
|
url: 'https://github.com/DLmaster361/AUTO_MAA.git',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ghfast',
|
||||||
|
name: 'ghfast 镜像',
|
||||||
|
url: 'https://ghfast.top/https://github.com/DLmaster361/AUTO_MAA.git',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ghproxy_cloudflare',
|
||||||
|
name: 'gh-proxy (Cloudflare加速)',
|
||||||
|
url: 'https://gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ghproxy_hongkong',
|
||||||
|
name: 'gh-proxy (香港节点加速)',
|
||||||
|
url: 'https://hk.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ghproxy_fastly',
|
||||||
|
name: 'gh-proxy (Fastly CDN加速)',
|
||||||
|
url: 'https://cdn.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ghproxy_edgeone',
|
||||||
|
name: 'gh-proxy (EdgeOne加速)',
|
||||||
|
url: 'https://edgeone.gh-proxy.com/https://github.com/DLmaster361/AUTO_MAA.git',
|
||||||
|
speed: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Python 下载镜像源配置(3.12.0 embed版本)
|
||||||
|
*/
|
||||||
|
export const PYTHON_MIRRORS: MirrorConfig[] = [
|
||||||
|
{
|
||||||
|
key: 'official',
|
||||||
|
name: 'Python 官方',
|
||||||
|
url: 'https://www.python.org/ftp/python/3.12.0/python-3.12.0-embed-amd64.zip',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tsinghua',
|
||||||
|
name: '清华 TUNA 镜像',
|
||||||
|
url: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ustc',
|
||||||
|
name: '中科大镜像',
|
||||||
|
url: 'https://mirrors.ustc.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'huawei',
|
||||||
|
name: '华为云镜像',
|
||||||
|
url: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.12.0/python-3.12.0-embed-amd64.zip',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'aliyun',
|
||||||
|
name: '阿里云镜像',
|
||||||
|
url: 'https://mirrors.aliyun.com/python-release/windows/python-3.12.0-embed-amd64.zip',
|
||||||
|
speed: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PyPI pip 镜像源配置
|
||||||
|
*/
|
||||||
|
export const PIP_MIRRORS: MirrorConfig[] = [
|
||||||
|
{
|
||||||
|
key: 'official',
|
||||||
|
name: 'PyPI 官方',
|
||||||
|
url: 'https://pypi.org/simple/',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tsinghua',
|
||||||
|
name: '清华大学',
|
||||||
|
url: 'https://pypi.tuna.tsinghua.edu.cn/simple/',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ustc',
|
||||||
|
name: '中科大',
|
||||||
|
url: 'https://pypi.mirrors.ustc.edu.cn/simple/',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'aliyun',
|
||||||
|
name: '阿里云',
|
||||||
|
url: 'https://mirrors.aliyun.com/pypi/simple/',
|
||||||
|
speed: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'douban',
|
||||||
|
name: '豆瓣',
|
||||||
|
url: 'https://pypi.douban.com/simple/',
|
||||||
|
speed: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 服务端点配置
|
||||||
|
*/
|
||||||
|
export const API_ENDPOINTS = {
|
||||||
|
// 本地开发服务器
|
||||||
|
local: 'http://localhost:8000',
|
||||||
|
// WebSocket连接基础URL
|
||||||
|
websocket: 'ws://localhost:8000',
|
||||||
|
// 代理服务器示例
|
||||||
|
proxy: 'http://127.0.0.1:7890'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自建下载站链接配置
|
||||||
|
*/
|
||||||
|
export const DOWNLOAD_LINKS = {
|
||||||
|
// get-pip.py 下载链接
|
||||||
|
getPip: 'http://221.236.27.82:10197/d/AUTO_MAA/get-pip.py',
|
||||||
|
|
||||||
|
// Git 客户端下载链接
|
||||||
|
git: 'http://221.236.27.82:10197/d/AUTO_MAA/git.zip'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有镜像源配置的集合
|
||||||
|
*/
|
||||||
|
export const ALL_MIRRORS: MirrorCategory = {
|
||||||
|
git: GIT_MIRRORS,
|
||||||
|
python: PYTHON_MIRRORS,
|
||||||
|
pip: PIP_MIRRORS
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型获取镜像源配置
|
||||||
|
*/
|
||||||
|
export function getMirrorsByType(type: keyof MirrorCategory): MirrorConfig[] {
|
||||||
|
return ALL_MIRRORS[type] || []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型和key获取特定镜像源URL
|
||||||
|
*/
|
||||||
|
export function getMirrorUrl(type: keyof MirrorCategory, key: string): string {
|
||||||
|
const mirrors = getMirrorsByType(type)
|
||||||
|
const mirror = mirrors.find(m => m.key === key)
|
||||||
|
return mirror?.url || mirrors[0]?.url || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认镜像源(通常是第一个)
|
||||||
|
*/
|
||||||
|
export function getDefaultMirror(type: keyof MirrorCategory): MirrorConfig | null {
|
||||||
|
const mirrors = getMirrorsByType(type)
|
||||||
|
return mirrors.length > 0 ? mirrors[0] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新镜像源速度测试结果
|
||||||
|
*/
|
||||||
|
export function updateMirrorSpeed(type: keyof MirrorCategory, key: string, speed: number): void {
|
||||||
|
const mirrors = getMirrorsByType(type)
|
||||||
|
const mirror = mirrors.find(m => m.key === key)
|
||||||
|
if (mirror) {
|
||||||
|
mirror.speed = speed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据速度排序镜像源
|
||||||
|
*/
|
||||||
|
export function sortMirrorsBySpeed(mirrors: MirrorConfig[]): MirrorConfig[] {
|
||||||
|
return [...mirrors].sort((a, b) => {
|
||||||
|
const speedA = a.speed === null ? 9999 : a.speed
|
||||||
|
const speedB = b.speed === null ? 9999 : b.speed
|
||||||
|
return speedA - speedB
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最快的镜像源
|
||||||
|
*/
|
||||||
|
export function getFastestMirror(type: keyof MirrorCategory): MirrorConfig | null {
|
||||||
|
const mirrors = getMirrorsByType(type)
|
||||||
|
const sortedMirrors = sortMirrorsBySpeed(mirrors)
|
||||||
|
return sortedMirrors.find(m => m.speed !== null && m.speed !== 9999) || null
|
||||||
|
}
|
||||||
@@ -13,8 +13,10 @@ import 'dayjs/locale/zh-cn'
|
|||||||
// 配置dayjs中文本地化
|
// 配置dayjs中文本地化
|
||||||
dayjs.locale('zh-cn')
|
dayjs.locale('zh-cn')
|
||||||
|
|
||||||
|
import { API_ENDPOINTS } from '@/config/mirrors'
|
||||||
|
|
||||||
// 配置API基础URL
|
// 配置API基础URL
|
||||||
OpenAPI.BASE = 'http://localhost:8000'
|
OpenAPI.BASE = API_ENDPOINTS.local
|
||||||
|
|
||||||
// 创建应用实例
|
// 创建应用实例
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|||||||
187
frontend/src/utils/mirrorManager.ts
Normal file
187
frontend/src/utils/mirrorManager.ts
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
/**
|
||||||
|
* 镜像源管理工具
|
||||||
|
* 提供动态获取和更新镜像源配置的功能
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ALL_MIRRORS,
|
||||||
|
API_ENDPOINTS,
|
||||||
|
DOWNLOAD_LINKS,
|
||||||
|
type MirrorConfig,
|
||||||
|
type MirrorCategory,
|
||||||
|
getMirrorUrl,
|
||||||
|
updateMirrorSpeed,
|
||||||
|
sortMirrorsBySpeed,
|
||||||
|
getFastestMirror
|
||||||
|
} from '@/config/mirrors'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 镜像源管理器类
|
||||||
|
*/
|
||||||
|
export class MirrorManager {
|
||||||
|
private static instance: MirrorManager
|
||||||
|
private mirrorConfigs: MirrorCategory = { ...ALL_MIRRORS }
|
||||||
|
private apiEndpoints = { ...API_ENDPOINTS }
|
||||||
|
private downloadLinks = { ...DOWNLOAD_LINKS }
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单例实例
|
||||||
|
*/
|
||||||
|
static getInstance(): MirrorManager {
|
||||||
|
if (!MirrorManager.instance) {
|
||||||
|
MirrorManager.instance = new MirrorManager()
|
||||||
|
}
|
||||||
|
return MirrorManager.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定类型的镜像源列表
|
||||||
|
*/
|
||||||
|
getMirrors(type: keyof MirrorCategory): MirrorConfig[] {
|
||||||
|
return this.mirrorConfigs[type] || []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取镜像源URL
|
||||||
|
*/
|
||||||
|
getMirrorUrl(type: keyof MirrorCategory, key: string): string {
|
||||||
|
return getMirrorUrl(type, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新镜像源速度
|
||||||
|
*/
|
||||||
|
updateMirrorSpeed(type: keyof MirrorCategory, key: string, speed: number): void {
|
||||||
|
updateMirrorSpeed(type, key, speed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最快的镜像源
|
||||||
|
*/
|
||||||
|
getFastestMirror(type: keyof MirrorCategory): MirrorConfig | null {
|
||||||
|
return getFastestMirror(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按速度排序镜像源
|
||||||
|
*/
|
||||||
|
sortMirrorsBySpeed(mirrors: MirrorConfig[]): MirrorConfig[] {
|
||||||
|
return sortMirrorsBySpeed(mirrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试镜像源速度
|
||||||
|
*/
|
||||||
|
async testMirrorSpeed(url: string, timeout: number = 5000): Promise<number> {
|
||||||
|
try {
|
||||||
|
const startTime = Date.now()
|
||||||
|
const controller = new AbortController()
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'HEAD',
|
||||||
|
signal: controller.signal,
|
||||||
|
cache: 'no-cache'
|
||||||
|
})
|
||||||
|
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return Date.now() - startTime
|
||||||
|
} else {
|
||||||
|
return 9999 // 请求失败
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return 9999 // 超时或网络错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量测试镜像源速度
|
||||||
|
*/
|
||||||
|
async testAllMirrorSpeeds(type: keyof MirrorCategory): Promise<MirrorConfig[]> {
|
||||||
|
const mirrors = this.getMirrors(type)
|
||||||
|
const promises = mirrors.map(async (mirror) => {
|
||||||
|
const speed = await this.testMirrorSpeed(mirror.url)
|
||||||
|
this.updateMirrorSpeed(type, mirror.key, speed)
|
||||||
|
return { ...mirror, speed }
|
||||||
|
})
|
||||||
|
|
||||||
|
const results = await Promise.all(promises)
|
||||||
|
return this.sortMirrorsBySpeed(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取API端点
|
||||||
|
*/
|
||||||
|
getApiEndpoint(key: keyof typeof API_ENDPOINTS): string {
|
||||||
|
return this.apiEndpoints[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取下载链接
|
||||||
|
*/
|
||||||
|
getDownloadLink(key: keyof typeof DOWNLOAD_LINKS): string {
|
||||||
|
return this.downloadLinks[key] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态更新镜像源配置(用于从API获取最新配置)
|
||||||
|
*/
|
||||||
|
updateMirrorConfig(type: keyof MirrorCategory, mirrors: MirrorConfig[]): void {
|
||||||
|
this.mirrorConfigs[type] = mirrors
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态更新API端点配置
|
||||||
|
*/
|
||||||
|
updateApiEndpoints(endpoints: Partial<typeof API_ENDPOINTS>): void {
|
||||||
|
this.apiEndpoints = { ...this.apiEndpoints, ...endpoints }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从远程API获取镜像源配置
|
||||||
|
*/
|
||||||
|
async fetchMirrorConfigFromApi(apiUrl: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${apiUrl}/api/mirrors`)
|
||||||
|
if (response.ok) {
|
||||||
|
const config = await response.json()
|
||||||
|
|
||||||
|
// 更新各类镜像源配置
|
||||||
|
if (config.git) this.updateMirrorConfig('git', config.git)
|
||||||
|
if (config.python) this.updateMirrorConfig('python', config.python)
|
||||||
|
if (config.pip) this.updateMirrorConfig('pip', config.pip)
|
||||||
|
|
||||||
|
// 更新API端点
|
||||||
|
if (config.apiEndpoints) this.updateApiEndpoints(config.apiEndpoints)
|
||||||
|
|
||||||
|
console.log('镜像源配置已从API更新')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('从API获取镜像源配置失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有镜像源配置(用于导出或备份)
|
||||||
|
*/
|
||||||
|
getAllConfigs() {
|
||||||
|
return {
|
||||||
|
mirrors: this.mirrorConfigs,
|
||||||
|
apiEndpoints: this.apiEndpoints,
|
||||||
|
downloadLinks: this.downloadLinks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出单例实例
|
||||||
|
export const mirrorManager = MirrorManager.getInstance()
|
||||||
|
|
||||||
|
// 导出便捷函数
|
||||||
|
export const getMirrors = (type: keyof MirrorCategory) => mirrorManager.getMirrors(type)
|
||||||
|
export const getMirrorUrlByManager = (type: keyof MirrorCategory, key: string) => mirrorManager.getMirrorUrl(type, key)
|
||||||
|
export const testMirrorSpeed = (url: string, timeout?: number) => mirrorManager.testMirrorSpeed(url, timeout)
|
||||||
|
export const testAllMirrorSpeeds = (type: keyof MirrorCategory) => mirrorManager.testAllMirrorSpeeds(type)
|
||||||
Reference in New Issue
Block a user