-
Notifications
You must be signed in to change notification settings - Fork 26
Add server failover rotation for classworkscloud provider #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f8f1b5d
bf9ff52
b084c80
a997e35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import axios from "@/axios/axios"; | ||
| import {getSetting} from "@/utils/settings"; | ||
| import {tryWithRotation, isRotationEnabled} from "@/utils/serverRotation"; | ||
|
|
||
| // Helper function to check if provider is valid for API calls | ||
| const isValidProvider = () => { | ||
|
|
@@ -33,9 +34,18 @@ export const getNamespaceInfo = async () => { | |
| throw new Error("当前数据提供者不支持此操作"); | ||
| } | ||
|
|
||
| const serverUrl = getSetting("server.domain"); | ||
|
|
||
| try { | ||
| // Use rotation for classworkscloud provider | ||
| if (isRotationEnabled()) { | ||
| const response = await tryWithRotation(async (serverUrl) => { | ||
| return await axios.get(`${serverUrl}/kv/_info`, { | ||
| headers: getHeaders(), | ||
| }); | ||
| }); | ||
| return response.data; | ||
|
Comment on lines
+39
to
+45
|
||
| } | ||
|
|
||
| const serverUrl = getSetting("server.domain"); | ||
| const response = await axios.get(`${serverUrl}/kv/_info`, { | ||
| headers: getHeaders(), | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import axios from "@/axios/axios"; | ||
| import {formatResponse, formatError} from "../dataProvider"; | ||
| import {getSetting} from "../settings"; | ||
| import {tryWithRotation, isRotationEnabled} from "../serverRotation"; | ||
|
|
||
| // Helper function to get request headers with kvtoken | ||
| const getHeaders = () => { | ||
|
|
@@ -22,9 +23,18 @@ const getHeaders = () => { | |
| export const kvServerProvider = { | ||
| async loadNamespaceInfo() { | ||
| try { | ||
| // 使用 Classworks Cloud 或者用户配置的服务器域名 | ||
| const serverUrl = getSetting("server.domain"); | ||
| // Use rotation for classworkscloud provider | ||
| if (isRotationEnabled()) { | ||
| return await tryWithRotation(async (serverUrl) => { | ||
| const res = await axios.get(`${serverUrl}/kv/_info`, { | ||
| headers: getHeaders(), | ||
| }); | ||
| return formatResponse(res.data); | ||
| }); | ||
|
Comment on lines
+27
to
+33
|
||
| } | ||
|
|
||
| // Standard single-server mode | ||
| const serverUrl = getSetting("server.domain"); | ||
| const res = await axios.get(`${serverUrl}/kv/_info`, { | ||
| headers: getHeaders(), | ||
| }); | ||
|
|
@@ -42,8 +52,17 @@ export const kvServerProvider = { | |
|
|
||
| async updateNamespaceInfo(data) { | ||
| try { | ||
| const serverUrl = getSetting("server.domain"); | ||
| // Use rotation for classworkscloud provider | ||
| if (isRotationEnabled()) { | ||
| return await tryWithRotation(async (serverUrl) => { | ||
| const res = await axios.put(`${serverUrl}/kv/_info`, data, { | ||
| headers: getHeaders(), | ||
| }); | ||
| return res; | ||
| }); | ||
|
Comment on lines
+56
to
+62
|
||
| } | ||
|
|
||
| const serverUrl = getSetting("server.domain"); | ||
| const res = await axios.put(`${serverUrl}/kv/_info`, data, { | ||
| headers: getHeaders(), | ||
| }); | ||
|
|
@@ -59,8 +78,17 @@ export const kvServerProvider = { | |
|
|
||
| async loadData(key) { | ||
| try { | ||
| const serverUrl = getSetting("server.domain"); | ||
| // Use rotation for classworkscloud provider | ||
| if (isRotationEnabled()) { | ||
| return await tryWithRotation(async (serverUrl) => { | ||
| const res = await axios.get(`${serverUrl}/kv/${key}`, { | ||
| headers: getHeaders(), | ||
| }); | ||
| return formatResponse(res.data); | ||
| }); | ||
|
Comment on lines
+82
to
+88
|
||
| } | ||
|
|
||
| const serverUrl = getSetting("server.domain"); | ||
| const res = await axios.get(`${serverUrl}/kv/${key}`, { | ||
| headers: getHeaders(), | ||
| }); | ||
|
|
@@ -80,6 +108,16 @@ export const kvServerProvider = { | |
|
|
||
| async saveData(key, data) { | ||
| try { | ||
| // Use rotation for classworkscloud provider | ||
| if (isRotationEnabled()) { | ||
| return await tryWithRotation(async (serverUrl) => { | ||
| await axios.post(`${serverUrl}/kv/${key}`, data, { | ||
| headers: getHeaders(), | ||
| }); | ||
| return formatResponse(true); | ||
| }); | ||
|
Comment on lines
+112
to
+118
|
||
| } | ||
|
|
||
| const serverUrl = getSetting("server.domain"); | ||
| await axios.post(`${serverUrl}/kv/${key}`, data, { | ||
| headers: getHeaders(), | ||
|
|
@@ -117,8 +155,6 @@ export const kvServerProvider = { | |
| */ | ||
| async loadKeys(options = {}) { | ||
| try { | ||
| const serverUrl = getSetting("server.domain"); | ||
|
|
||
| // 设置默认参数 | ||
| const { | ||
| sortBy = "key", | ||
|
|
@@ -135,6 +171,17 @@ export const kvServerProvider = { | |
| skip: skip.toString() | ||
| }); | ||
|
|
||
| // Use rotation for classworkscloud provider | ||
| if (isRotationEnabled()) { | ||
| return await tryWithRotation(async (serverUrl) => { | ||
| const res = await axios.get(`${serverUrl}/kv/_keys?${params}`, { | ||
| headers: getHeaders(), | ||
| }); | ||
| return formatResponse(res.data); | ||
| }); | ||
|
Comment on lines
+175
to
+181
|
||
| } | ||
|
|
||
| const serverUrl = getSetting("server.domain"); | ||
| const res = await axios.get(`${serverUrl}/kv/_keys?${params}`, { | ||
| headers: getHeaders(), | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,106 @@ | ||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||
| * Server rotation utility for Classworks Cloud provider | ||||||||||||||||||||||||
| * Provides fallback mechanism across multiple server endpoints | ||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import { getSetting } from "./settings"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Server list for classworkscloud provider (in priority order) | ||||||||||||||||||||||||
| const CLASSWORKS_CLOUD_SERVERS = [ | ||||||||||||||||||||||||
| "https://kv-service.houlang.cloud", | ||||||||||||||||||||||||
| "https://kv-service.wuyuan.dev", | ||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||
| * Get the list of servers to try for the given provider | ||||||||||||||||||||||||
| * @param {string} provider - The provider type | ||||||||||||||||||||||||
| * @returns {string[]} Array of server URLs to try | ||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||
| export function getServerList(provider) { | ||||||||||||||||||||||||
| if (provider === "classworkscloud") { | ||||||||||||||||||||||||
| return [...CLASSWORKS_CLOUD_SERVERS]; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // For other providers, use the configured domain | ||||||||||||||||||||||||
| const domain = getSetting("server.domain"); | ||||||||||||||||||||||||
| return domain ? [domain] : []; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||
| * Try an operation with server rotation fallback | ||||||||||||||||||||||||
| * @param {Function} operation - Async function that takes a serverUrl and returns a promise | ||||||||||||||||||||||||
| * @param {Object} options - Options | ||||||||||||||||||||||||
| * @param {string} options.provider - Provider type (optional, defaults to current setting) | ||||||||||||||||||||||||
| * @param {Function} options.onServerTried - Callback called when a server is tried (optional) | ||||||||||||||||||||||||
| * Receives: { url, status, tried } where tried is a snapshot of attempts | ||||||||||||||||||||||||
| * @returns {Promise} Result from the first successful server, or throws the last error | ||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||
| export async function tryWithRotation(operation, options = {}) { | ||||||||||||||||||||||||
| const provider = options.provider || getSetting("server.provider"); | ||||||||||||||||||||||||
| const onServerTried = options.onServerTried; | ||||||||||||||||||||||||
| const hasCallback = typeof onServerTried === 'function'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const servers = getServerList(provider); | ||||||||||||||||||||||||
| const triedServers = []; | ||||||||||||||||||||||||
| let lastError = null; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| for (const serverUrl of servers) { | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| triedServers.push({ url: serverUrl, status: "trying" }); | ||||||||||||||||||||||||
| if (hasCallback) { | ||||||||||||||||||||||||
| // Provide a snapshot to prevent callback from mutating internal state | ||||||||||||||||||||||||
| onServerTried({ url: serverUrl, status: "trying", tried: [...triedServers] }); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const result = await operation(serverUrl); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| triedServers[triedServers.length - 1].status = "success"; | ||||||||||||||||||||||||
| if (hasCallback) { | ||||||||||||||||||||||||
| onServerTried({ url: serverUrl, status: "success", tried: [...triedServers] }); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return result; | ||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||
| lastError = error; | ||||||||||||||||||||||||
| triedServers[triedServers.length - 1].status = "failed"; | ||||||||||||||||||||||||
| triedServers[triedServers.length - 1].error = error.message || String(error); | ||||||||||||||||||||||||
| if (hasCallback) { | ||||||||||||||||||||||||
| onServerTried({ url: serverUrl, status: "failed", error, tried: [...triedServers] }); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Continue to next server | ||||||||||||||||||||||||
| console.warn(`Server ${serverUrl} failed:`, error.message); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // All servers failed | ||||||||||||||||||||||||
| console.error("All servers failed. Tried:", triedServers); | ||||||||||||||||||||||||
| const error = lastError || new Error("All servers failed"); | ||||||||||||||||||||||||
| error.triedServers = triedServers; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| error.triedServers = triedServers; | |
| error.triedServers = triedServers; | |
| // Ensure downstream handlers that expect an axios-style error.response do not break | |
| if (!error.response) { | |
| error.response = { | |
| data: { | |
| message: error.message || "All servers failed", | |
| triedServers, | |
| }, | |
| }; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message formatting has inconsistent spacing. Line 206 shows "失败${s.error ?
: ${s.error}: ""}" which will produce output like "失败: error message" (with Chinese characters followed by a colon and space). However, in Chinese text formatting, it's more conventional to use Chinese punctuation (如:"失败:error message") or omit the space. Consider using either "失败: ${s.error}" with proper Chinese formatting or ensuring consistent spacing throughout the user-facing messages.