diff --git a/server/Program.cs b/server/Program.cs index 49b62f5..3901dfd 100644 --- a/server/Program.cs +++ b/server/Program.cs @@ -95,6 +95,12 @@ try .AllowAnyMethod() .AllowAnyHeader() ); + options.AddPolicy("SignalR", policy => policy + .WithOrigins("http://localhost:5173") + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials() + ); }); // Use SignalR @@ -201,12 +207,15 @@ try }; }); app.UseSwaggerUi(); + + // SignalR + app.UseWebSockets(); app.UseSignalRHubSpecification(); app.UseSignalRHubDevelopmentUI(); // Router app.MapControllers(); - app.MapHub("hubs/JtagHub").RequireCors("Users"); + app.MapHub("hubs/JtagHub"); // Setup Program MsgBus.Init(); diff --git a/server/src/Hubs/JtagHub.cs b/server/src/Hubs/JtagHub.cs index 7647043..8274a8d 100644 --- a/server/src/Hubs/JtagHub.cs +++ b/server/src/Hubs/JtagHub.cs @@ -25,12 +25,19 @@ public interface IJtagReceiver } [Authorize] -[EnableCors("Users")] +[EnableCors("SignalR")] public class JtagHub : Hub, IJtagHub { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); - private ConcurrentDictionary FreqTable = new(); - private ConcurrentDictionary CancellationTokenSourceTable = new(); + private static ConcurrentDictionary FreqTable = new(); + private static ConcurrentDictionary CancellationTokenSourceTable = new(); + + private readonly IHubContext _hubContext; + + public JtagHub(IHubContext hubContext) + { + _hubContext = hubContext; + } private Optional GetJtagClient(string userName) { @@ -92,33 +99,40 @@ public class JtagHub : Hub, IJtagHub } await SetBoundaryScanFreq(freq); - var cts = CancellationTokenSource.CreateLinkedTokenSource(); + var cts = new CancellationTokenSource(); CancellationTokenSourceTable.AddOrUpdate(userName, cts, (key, value) => cts); - _ = Task - .Run( - () => BoundaryScanLogicPorts( - Context.ConnectionId, - userName, - cts.Token), + _ = Task.Run( + () => BoundaryScanLogicPorts(Context.ConnectionId, userName, cts.Token), cts.Token) .ContinueWith((task) => { - if (!task.IsFaulted) + if (task.IsFaulted) { - return; + // 遍历所有异常 + foreach (var ex in task.Exception.InnerExceptions) + { + if (ex is OperationCanceledException) + { + logger.Info($"Boundary scan operation cancelled for user {userName}"); + } + else + { + logger.Error($"Boundary scan operation failed for user {userName}: {ex}"); + } + } } - - if (task.Exception.InnerException is OperationCanceledException) + else if (task.IsCanceled) { logger.Info($"Boundary scan operation cancelled for user {userName}"); } else { - logger.Error(task.Exception); + logger.Info($"Boundary scan completed successfully for user {userName}"); } }); + logger.Info($"Boundary scan started for user {userName}"); return true; } catch (Exception error) @@ -144,10 +158,12 @@ public class JtagHub : Hub, IJtagHub cts.Cancel(); cts.Token.WaitHandle.WaitOne(); + + logger.Info($"Boundary scan stopped for user {userName}"); return true; } - private async void BoundaryScanLogicPorts(string connectionID, string userName, CancellationToken cancellationToken) + private async Task BoundaryScanLogicPorts(string connectionID, string userName, CancellationToken cancellationToken) { var jtagCtrl = GetJtagClient(userName).OrThrow(() => new InvalidOperationException("JTAG client not found")); var cntFail = 0; @@ -161,9 +177,10 @@ public class JtagHub : Hub, IJtagHub { logger.Error($"User {userName} boundary scan failed for device {jtagCtrl.address}: {ret.Error}"); cntFail++; + continue; } - await this.Clients.Client(connectionID).OnReceiveBoundaryScanData(ret.Value); + await _hubContext.Clients.Client(connectionID).OnReceiveBoundaryScanData(ret.Value); // logger.Info($"User {userName} successfully completed boundary scan for device {jtagCtrl.address}"); await Task.Delay(FreqTable.TryGetValue(userName, out var freq) ? 1000 / freq : 1000 / 100, cancellationToken); diff --git a/src/stores/equipments.ts b/src/stores/equipments.ts index 1d3a19d..4074466 100644 --- a/src/stores/equipments.ts +++ b/src/stores/equipments.ts @@ -1,7 +1,7 @@ import { ref, reactive, watchPostEffect, onMounted, onUnmounted } from "vue"; import { defineStore } from "pinia"; import { useLocalStorage } from "@vueuse/core"; -import { isString, toNumber, type Dictionary } from "lodash"; +import { isString, toNumber, isUndefined, type Dictionary } from "lodash"; import z from "zod"; import { isNumber } from "mathjs"; import { Mutex, withTimeout } from "async-mutex"; @@ -9,8 +9,9 @@ import { useConstraintsStore } from "@/stores/constraints"; import { useDialogStore } from "./dialog"; import { toFileParameterOrUndefined } from "@/utils/Common"; import { AuthManager } from "@/utils/AuthManager"; -import { HubConnectionBuilder } from "@microsoft/signalr"; +import { HubConnection, HubConnectionBuilder } from "@microsoft/signalr"; import { getHubProxyFactory, getReceiverRegister } from "@/TypedSignalR.Client"; +import type { IJtagHub } from "@/TypedSignalR.Client/server.Hubs.JtagHub"; export const useEquipments = defineStore("equipments", () => { // Global Stores @@ -28,22 +29,32 @@ export const useEquipments = defineStore("equipments", () => { 1000, new Error("JtagClient Mutex Timeout!"), ); - const jtagHubConnection = new HubConnectionBuilder() - .withUrl("http://localhost:5000/hubs/JtagHub") - .withAutomaticReconnect() - .build(); - const jtagHubProxy = - getHubProxyFactory("IJtagHub").createHubProxy(jtagHubConnection); - const jtagHubSubscription = getReceiverRegister("IJtagReceiver").register( - jtagHubConnection, - { + const jtagHubConnection = ref(); + const jtagHubProxy = ref(); + + onMounted(async () => { + // 每次挂载都重新创建连接 + jtagHubConnection.value = + AuthManager.createAuthenticatedJtagHubConnection(); + jtagHubProxy.value = getHubProxyFactory("IJtagHub").createHubProxy( + jtagHubConnection.value, + ); + + getReceiverRegister("IJtagReceiver").register(jtagHubConnection.value, { onReceiveBoundaryScanData: async (msg) => { constrainsts.batchSetConstraintStates(msg); }, - }, - ); - onMounted(() => { + }); + await jtagHubConnection.value.start(); + }); + onUnmounted(() => { + // 断开连接,清理资源 + if (jtagHubConnection.value) { + jtagHubConnection.value.stop(); + jtagHubConnection.value = undefined; + jtagHubProxy.value = undefined; + } }); // Matrix Key @@ -87,13 +98,27 @@ export const useEquipments = defineStore("equipments", () => { } async function jtagBoundaryScanSetOnOff(enable: boolean) { - jtagHubConnection.start(); - enableJtagBoundaryScan.value = enable; - if (enable) { - jtagHubProxy.startBoundaryScan(jtagBoundaryScanFreq.value); - } else { - jtagHubProxy.stopBoundaryScan(); + if (isUndefined(jtagHubProxy.value)) { + console.error("JtagHub Not Initialize..."); + return; } + + if (enable) { + const ret = await jtagHubProxy.value.startBoundaryScan( + jtagBoundaryScanFreq.value, + ); + if (!ret) { + console.error("Failed to start boundary scan"); + return; + } + } else { + const ret = await jtagHubProxy.value.stopBoundaryScan(); + if (!ret) { + console.error("Failed to stop boundary scan"); + return; + } + } + enableJtagBoundaryScan.value = enable; } async function jtagUploadBitstream(bitstream: File): Promise { diff --git a/src/utils/AuthManager.ts b/src/utils/AuthManager.ts index 25750ae..b0c47c1 100644 --- a/src/utils/AuthManager.ts +++ b/src/utils/AuthManager.ts @@ -15,7 +15,10 @@ import { DebuggerClient, ExamClient, } from "@/APIClient"; +import router from "@/router"; +import { HubConnectionBuilder } from "@microsoft/signalr"; import axios, { type AxiosInstance } from "axios"; +import { isNull } from "lodash"; // 支持的客户端类型联合类型 type SupportedClient = @@ -117,7 +120,7 @@ export class AuthManager { if (!token) return null; const instance = axios.create(); - instance.interceptors.request.use(config => { + instance.interceptors.request.use((config) => { config.headers = config.headers || {}; (config.headers as any)["Authorization"] = `Bearer ${token}`; return config; @@ -183,11 +186,11 @@ export class AuthManager { public static createAuthenticatedNetConfigClient(): NetConfigClient { return AuthManager.createAuthenticatedClient(NetConfigClient); } - + public static createAuthenticatedOscilloscopeApiClient(): OscilloscopeApiClient { return AuthManager.createAuthenticatedClient(OscilloscopeApiClient); } - + public static createAuthenticatedDebuggerClient(): DebuggerClient { return AuthManager.createAuthenticatedClient(DebuggerClient); } @@ -196,6 +199,21 @@ export class AuthManager { return AuthManager.createAuthenticatedClient(ExamClient); } + public static createAuthenticatedJtagHubConnection() { + const token = this.getToken(); + if (isNull(token)) { + router.push("/login"); + throw Error("Token Null!"); + } + + return new HubConnectionBuilder() + .withUrl("http://127.0.0.1:5000/hubs/JtagHub", { + accessTokenFactory: () => token, + }) + .withAutomaticReconnect() + .build(); + } + // 登录函数 public static async login( username: string, diff --git a/vite.config.ts b/vite.config.ts index c225610..fdbef4f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -53,10 +53,6 @@ export default defineConfig({ target: "http://localhost:5000", changeOrigin: true, }, - "/hubs": { - target: "http://localhost:5000", - changeOrigin: true, - }, }, port: 5173, },