mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2026-02-04 16:03:10 +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 SettingsCard from "@/components/SettingsCard.vue";
|
||||||
import {getSetting} from "@/utils/settings";
|
import {getSetting} from "@/utils/settings";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import {tryWithRotation, isRotationEnabled} from "@/utils/serverRotation";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DataProviderSettingsCard",
|
name: "DataProviderSettingsCard",
|
||||||
@ -132,8 +133,9 @@ export default {
|
|||||||
async checkServerConnection() {
|
async checkServerConnection() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.serverchecktime = new Date();
|
this.serverchecktime = new Date();
|
||||||
|
const triedServers = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const domain = getSetting("server.domain");
|
|
||||||
const siteKey = getSetting("server.siteKey");
|
const siteKey = getSetting("server.siteKey");
|
||||||
|
|
||||||
// Prepare headers including site key if available
|
// Prepare headers including site key if available
|
||||||
@ -142,21 +144,71 @@ export default {
|
|||||||
headers["x-site-key"] = siteKey;
|
headers["x-site-key"] = siteKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios.get(`${domain}/check`, {
|
// Use rotation for classworkscloud provider
|
||||||
method: "GET",
|
if (isRotationEnabled()) {
|
||||||
headers,
|
const response = await tryWithRotation(
|
||||||
});
|
async (serverUrl) => {
|
||||||
|
const res = await axios.get(`${serverUrl}/check`, {
|
||||||
if (response.data.status === "success") {
|
method: "GET",
|
||||||
this.$message.success(
|
headers,
|
||||||
"连接成功",
|
});
|
||||||
"服务器连接正常 延迟" + (new Date() - this.serverchecktime) + "ms"
|
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 {
|
} 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) {
|
} 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 {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import axios from "@/axios/axios";
|
import axios from "@/axios/axios";
|
||||||
import {getSetting} from "@/utils/settings";
|
import {getSetting} from "@/utils/settings";
|
||||||
|
import {tryWithRotation, isRotationEnabled} from "@/utils/serverRotation";
|
||||||
|
|
||||||
// Helper function to check if provider is valid for API calls
|
// Helper function to check if provider is valid for API calls
|
||||||
const isValidProvider = () => {
|
const isValidProvider = () => {
|
||||||
@ -33,9 +34,18 @@ export const getNamespaceInfo = async () => {
|
|||||||
throw new Error("当前数据提供者不支持此操作");
|
throw new Error("当前数据提供者不支持此操作");
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverUrl = getSetting("server.domain");
|
|
||||||
|
|
||||||
try {
|
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`, {
|
const response = await axios.get(`${serverUrl}/kv/_info`, {
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import {kvLocalProvider} from "./providers/kvLocalProvider";
|
import {kvLocalProvider} from "./providers/kvLocalProvider";
|
||||||
import {kvServerProvider} from "./providers/kvServerProvider";
|
import {kvServerProvider} from "./providers/kvServerProvider";
|
||||||
import {getSetting, setSetting} from "./settings";
|
import {getSetting, setSetting} from "./settings";
|
||||||
|
import {getEffectiveServerUrl} from "./serverRotation";
|
||||||
|
|
||||||
export const formatResponse = (data) => data;
|
export const formatResponse = (data) => data;
|
||||||
|
|
||||||
@ -173,7 +174,16 @@ export default {
|
|||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
try {
|
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");
|
let siteKey = getSetting("server.siteKey");
|
||||||
const machineId = getSetting("device.uuid");
|
const machineId = getSetting("device.uuid");
|
||||||
let configured = false;
|
let configured = false;
|
||||||
@ -200,6 +210,8 @@ export default {
|
|||||||
|
|
||||||
// 设置provider为classworkscloud
|
// 设置provider为classworkscloud
|
||||||
setSetting("server.provider", "classworkscloud");
|
setSetting("server.provider", "classworkscloud");
|
||||||
|
// Get effective URL after setting provider
|
||||||
|
serverUrl = getEffectiveServerUrl();
|
||||||
} else {
|
} else {
|
||||||
return formatError("云端配置无效,请检查服务器域名和设备UUID", "CONFIG_ERROR");
|
return formatError("云端配置无效,请检查服务器域名和设备UUID", "CONFIG_ERROR");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import axios from "@/axios/axios";
|
import axios from "@/axios/axios";
|
||||||
import {formatResponse, formatError} from "../dataProvider";
|
import {formatResponse, formatError} from "../dataProvider";
|
||||||
import {getSetting} from "../settings";
|
import {getSetting} from "../settings";
|
||||||
|
import {tryWithRotation, isRotationEnabled} from "../serverRotation";
|
||||||
|
|
||||||
// Helper function to get request headers with kvtoken
|
// Helper function to get request headers with kvtoken
|
||||||
const getHeaders = () => {
|
const getHeaders = () => {
|
||||||
@ -22,9 +23,18 @@ const getHeaders = () => {
|
|||||||
export const kvServerProvider = {
|
export const kvServerProvider = {
|
||||||
async loadNamespaceInfo() {
|
async loadNamespaceInfo() {
|
||||||
try {
|
try {
|
||||||
// 使用 Classworks Cloud 或者用户配置的服务器域名
|
// Use rotation for classworkscloud provider
|
||||||
const serverUrl = getSetting("server.domain");
|
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`, {
|
const res = await axios.get(`${serverUrl}/kv/_info`, {
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
});
|
});
|
||||||
@ -42,8 +52,17 @@ export const kvServerProvider = {
|
|||||||
|
|
||||||
async updateNamespaceInfo(data) {
|
async updateNamespaceInfo(data) {
|
||||||
try {
|
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, {
|
const res = await axios.put(`${serverUrl}/kv/_info`, data, {
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
});
|
});
|
||||||
@ -59,8 +78,17 @@ export const kvServerProvider = {
|
|||||||
|
|
||||||
async loadData(key) {
|
async loadData(key) {
|
||||||
try {
|
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}`, {
|
const res = await axios.get(`${serverUrl}/kv/${key}`, {
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
});
|
});
|
||||||
@ -80,6 +108,16 @@ export const kvServerProvider = {
|
|||||||
|
|
||||||
async saveData(key, data) {
|
async saveData(key, data) {
|
||||||
try {
|
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");
|
const serverUrl = getSetting("server.domain");
|
||||||
await axios.post(`${serverUrl}/kv/${key}`, data, {
|
await axios.post(`${serverUrl}/kv/${key}`, data, {
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
@ -117,8 +155,6 @@ export const kvServerProvider = {
|
|||||||
*/
|
*/
|
||||||
async loadKeys(options = {}) {
|
async loadKeys(options = {}) {
|
||||||
try {
|
try {
|
||||||
const serverUrl = getSetting("server.domain");
|
|
||||||
|
|
||||||
// 设置默认参数
|
// 设置默认参数
|
||||||
const {
|
const {
|
||||||
sortBy = "key",
|
sortBy = "key",
|
||||||
@ -135,6 +171,17 @@ export const kvServerProvider = {
|
|||||||
skip: skip.toString()
|
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}`, {
|
const res = await axios.get(`${serverUrl}/kv/_keys?${params}`, {
|
||||||
headers: getHeaders(),
|
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 {io} from 'socket.io-client';
|
||||||
import {getSetting} from '@/utils/settings';
|
import {getSetting} from '@/utils/settings';
|
||||||
|
import {getEffectiveServerUrl, isRotationEnabled, tryWithRotation} from '@/utils/serverRotation';
|
||||||
|
|
||||||
let socket = null;
|
let socket = null;
|
||||||
let connectedDomain = null;
|
let connectedDomain = null;
|
||||||
const listeners = new Set();
|
const listeners = new Set();
|
||||||
|
|
||||||
export function getServerUrl() {
|
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
|
// Prefer configured server domain; fallback to env; then current origin
|
||||||
const cfg = getSetting('server.domain');
|
const cfg = getSetting('server.domain');
|
||||||
const envUrl = import.meta?.env?.VITE_SERVER_URL;
|
const envUrl = import.meta?.env?.VITE_SERVER_URL;
|
||||||
@ -28,6 +34,11 @@ export function getSocket() {
|
|||||||
socket = null;
|
socket = null;
|
||||||
}
|
}
|
||||||
connectedDomain = serverUrl;
|
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"]});
|
socket = io(serverUrl, {transports: ["polling","websocket"]});
|
||||||
|
|
||||||
// Re-attach previously registered event handlers on new socket instance
|
// Re-attach previously registered event handlers on new socket instance
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user