mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2026-02-04 07:53:11 +00:00
Compare commits
5 Commits
b905c4390c
...
7ce2deb61c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ce2deb61c | ||
|
|
a997e35162 | ||
|
|
b084c80b18 | ||
|
|
bf9ff52ee0 | ||
|
|
f8f1b5d494 |
@ -90,6 +90,7 @@
|
||||
import SettingsCard from "@/components/SettingsCard.vue";
|
||||
import {getSetting} from "@/utils/settings";
|
||||
import axios from "axios";
|
||||
import {tryWithRotation, isRotationEnabled} from "@/utils/serverRotation";
|
||||
|
||||
export default {
|
||||
name: "DataProviderSettingsCard",
|
||||
@ -132,8 +133,9 @@ export default {
|
||||
async checkServerConnection() {
|
||||
this.loading = true;
|
||||
this.serverchecktime = new Date();
|
||||
const triedServers = [];
|
||||
|
||||
try {
|
||||
const domain = getSetting("server.domain");
|
||||
const siteKey = getSetting("server.siteKey");
|
||||
|
||||
// Prepare headers including site key if available
|
||||
@ -142,21 +144,71 @@ export default {
|
||||
headers["x-site-key"] = siteKey;
|
||||
}
|
||||
|
||||
const response = await axios.get(`${domain}/check`, {
|
||||
method: "GET",
|
||||
headers,
|
||||
});
|
||||
|
||||
if (response.data.status === "success") {
|
||||
this.$message.success(
|
||||
"连接成功",
|
||||
"服务器连接正常 延迟" + (new Date() - this.serverchecktime) + "ms"
|
||||
// Use rotation for classworkscloud provider
|
||||
if (isRotationEnabled()) {
|
||||
const response = await tryWithRotation(
|
||||
async (serverUrl) => {
|
||||
const res = await axios.get(`${serverUrl}/check`, {
|
||||
method: "GET",
|
||||
headers,
|
||||
});
|
||||
if (res.data.status !== "success") {
|
||||
throw new Error("服务器响应异常");
|
||||
}
|
||||
return res;
|
||||
},
|
||||
{
|
||||
onServerTried: ({url, status, tried}) => {
|
||||
triedServers.length = 0;
|
||||
triedServers.push(...tried);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Build success message with tried servers info
|
||||
const latency = new Date() - this.serverchecktime;
|
||||
const successServer = triedServers.find(s => s.status === "success");
|
||||
let message = `服务器连接正常 延迟${latency}ms`;
|
||||
|
||||
if (triedServers.length > 1) {
|
||||
const serverList = triedServers.map((s, i) =>
|
||||
`${i + 1}. ${s.url} (${s.status === "success" ? "成功" : "失败"})`
|
||||
).join("\n");
|
||||
message += `\n\n依次尝试的服务器:\n${serverList}`;
|
||||
} else if (successServer) {
|
||||
message += `\n服务器: ${successServer.url}`;
|
||||
}
|
||||
|
||||
this.$message.success("连接成功", message);
|
||||
} else {
|
||||
throw new Error("服务器响应异常");
|
||||
// Standard single-server check for other providers
|
||||
const domain = getSetting("server.domain");
|
||||
const response = await axios.get(`${domain}/check`, {
|
||||
method: "GET",
|
||||
headers,
|
||||
});
|
||||
|
||||
if (response.data.status === "success") {
|
||||
this.$message.success(
|
||||
"连接成功",
|
||||
"服务器连接正常 延迟" + (new Date() - this.serverchecktime) + "ms"
|
||||
);
|
||||
} else {
|
||||
throw new Error("服务器响应异常");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("连接失败", error.message || "无法连接到服务器");
|
||||
// Build error message with tried servers info
|
||||
let errorMessage = error.message || "无法连接到服务器";
|
||||
|
||||
if (triedServers.length > 0) {
|
||||
const serverList = triedServers.map((s, i) =>
|
||||
`${i + 1}. ${s.url} (失败${s.error ? `: ${s.error}` : ""})`
|
||||
).join("\n");
|
||||
errorMessage += `\n\n依次尝试的服务器:\n${serverList}\n\n所有服务器均连接失败`;
|
||||
}
|
||||
|
||||
this.$message.error("连接失败", errorMessage);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
const serverUrl = getSetting("server.domain");
|
||||
const response = await axios.get(`${serverUrl}/kv/_info`, {
|
||||
headers: getHeaders(),
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {kvLocalProvider} from "./providers/kvLocalProvider";
|
||||
import {kvServerProvider} from "./providers/kvServerProvider";
|
||||
import {getSetting, setSetting} from "./settings";
|
||||
import {getEffectiveServerUrl} from "./serverRotation";
|
||||
|
||||
export const formatResponse = (data) => data;
|
||||
|
||||
@ -173,7 +174,16 @@ export default {
|
||||
} = options;
|
||||
|
||||
try {
|
||||
let serverUrl = getSetting("server.domain");
|
||||
const provider = getSetting("server.provider");
|
||||
let serverUrl;
|
||||
|
||||
// Use effective server URL for classworkscloud provider
|
||||
if (provider === "classworkscloud") {
|
||||
serverUrl = getEffectiveServerUrl();
|
||||
} else {
|
||||
serverUrl = getSetting("server.domain");
|
||||
}
|
||||
|
||||
let siteKey = getSetting("server.siteKey");
|
||||
const machineId = getSetting("device.uuid");
|
||||
let configured = false;
|
||||
@ -200,6 +210,8 @@ export default {
|
||||
|
||||
// 设置provider为classworkscloud
|
||||
setSetting("server.provider", "classworkscloud");
|
||||
// Get effective URL after setting provider
|
||||
serverUrl = getEffectiveServerUrl();
|
||||
} else {
|
||||
return formatError("云端配置无效,请检查服务器域名和设备UUID", "CONFIG_ERROR");
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
const serverUrl = getSetting("server.domain");
|
||||
const res = await axios.get(`${serverUrl}/kv/_keys?${params}`, {
|
||||
headers: getHeaders(),
|
||||
});
|
||||
|
||||
106
src/utils/serverRotation.js
Normal file
106
src/utils/serverRotation.js
Normal file
@ -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;
|
||||
throw error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective server URL for the current provider
|
||||
* For classworkscloud, returns the first server in the list
|
||||
* For other providers, returns the configured domain
|
||||
* @returns {string} Server URL
|
||||
*/
|
||||
export function getEffectiveServerUrl() {
|
||||
const provider = getSetting("server.provider");
|
||||
|
||||
if (provider === "classworkscloud") {
|
||||
return CLASSWORKS_CLOUD_SERVERS[0];
|
||||
}
|
||||
|
||||
return getSetting("server.domain") || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if rotation is enabled for the current provider
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isRotationEnabled() {
|
||||
const provider = getSetting("server.provider");
|
||||
return provider === "classworkscloud";
|
||||
}
|
||||
@ -4,12 +4,18 @@
|
||||
|
||||
import {io} from 'socket.io-client';
|
||||
import {getSetting} from '@/utils/settings';
|
||||
import {getEffectiveServerUrl, isRotationEnabled, tryWithRotation} from '@/utils/serverRotation';
|
||||
|
||||
let socket = null;
|
||||
let connectedDomain = null;
|
||||
const listeners = new Set();
|
||||
|
||||
export function getServerUrl() {
|
||||
// For classworkscloud provider, use the effective server URL from rotation
|
||||
if (isRotationEnabled()) {
|
||||
return getEffectiveServerUrl();
|
||||
}
|
||||
|
||||
// Prefer configured server domain; fallback to env; then current origin
|
||||
const cfg = getSetting('server.domain');
|
||||
const envUrl = import.meta?.env?.VITE_SERVER_URL;
|
||||
@ -28,6 +34,11 @@ export function getSocket() {
|
||||
socket = null;
|
||||
}
|
||||
connectedDomain = serverUrl;
|
||||
|
||||
// For classworkscloud, create socket with the first server in rotation
|
||||
// Note: Socket.IO's built-in reconnection will retry the same server URL.
|
||||
// Server rotation is handled at the HTTP request level, not Socket.IO level.
|
||||
// If the Socket.IO server goes down, the connection will fail until the server recovers.
|
||||
socket = io(serverUrl, {transports: ["polling","websocket"]});
|
||||
|
||||
// Re-attach previously registered event handlers on new socket instance
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user