From 0fb0c4e395140f2c3729504ac596aa34e5df35a9 Mon Sep 17 00:00:00 2001 From: SikongJueluo Date: Sat, 12 Jul 2025 14:59:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E7=AB=AF=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=A9=BA=E9=97=B2=E5=AE=9E=E9=AA=8C=E6=9D=BF?= =?UTF-8?q?=EF=BC=8C=E7=BB=A7=E7=BB=AD=E4=BF=AE=E6=94=B9=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E4=BD=BF=E5=85=B6=E6=9B=B4=E5=8A=A0=E5=90=88?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/Controllers/DataController.cs | 40 +++++- server/src/Database.cs | 151 ++++++++++++++-------- src/APIClient.ts | 66 ++++++++-- src/{views/User => utils}/BoardManager.ts | 0 src/views/HomeView.vue | 49 +------ src/views/Project/Index.vue | 21 ++- src/views/Project/Oscilloscope.vue | 2 +- src/views/Project/VideoStream.vue | 2 +- src/views/User/AddBoardDialog.vue | 2 +- src/views/User/BoardTable.vue | 2 +- src/views/User/BoardTableManager.ts | 4 +- 11 files changed, 222 insertions(+), 117 deletions(-) rename src/{views/User => utils}/BoardManager.ts (100%) diff --git a/server/src/Controllers/DataController.cs b/server/src/Controllers/DataController.cs index da1f4df..c2cb10a 100644 --- a/server/src/Controllers/DataController.cs +++ b/server/src/Controllers/DataController.cs @@ -1,5 +1,4 @@ using System.IdentityModel.Tokens.Jwt; -using System.Net; using System.Security.Claims; using System.Text; using Microsoft.AspNetCore.Authorization; @@ -192,6 +191,45 @@ public class DataController : ControllerBase } } + /// + /// 获取一个空闲的实验板(普通用户权限) + /// + [Authorize] + [HttpGet("GetAvailableBoard")] + [EnableCors("Users")] + [ProducesResponseType(typeof(Database.Board), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult GetAvailableBoard() + { + try + { + using var db = new Database.AppDataConnection(); + var boardOpt = db.GetAvailableBoard(); + if (!boardOpt.HasValue) + return NotFound("没有可用的实验板"); + + // 绑定用户与实验板 + var userName = User.Identity?.Name; + if (string.IsNullOrEmpty(userName)) + return Unauthorized("未找到用户名信息"); + + var userRet = db.GetUserByName(userName); + if (!userRet.IsSuccessful || !userRet.Value.HasValue) + return BadRequest("用户不存在"); + + var user = userRet.Value.Value; + db.BindUserToBoard(user.ID, boardOpt.Value.ID); + + return Ok(boardOpt.Value); + } + catch (Exception ex) + { + logger.Error(ex, "获取空闲实验板时发生异常"); + return StatusCode(StatusCodes.Status500InternalServerError, "获取失败,请稍后重试"); + } + } + /// /// 新增板子(管理员权限) /// diff --git a/server/src/Database.cs b/server/src/Database.cs index 7589ba4..0d31cb3 100644 --- a/server/src/Database.cs +++ b/server/src/Database.cs @@ -29,35 +29,35 @@ public class User public required string EMail { get; set; } /// - /// [TODO:description] + /// 用户的密码(应该进行哈希处理) /// [NotNull] public required string Password { get; set; } /// - /// [TODO:description] + /// 用户权限等级 /// [NotNull] public required UserPermission Permission { get; set; } /// - /// [TODO:description] + /// 绑定的实验板ID,如果未绑定则为空 /// [Nullable] public Guid BoardID { get; set; } /// - /// [TODO:description] + /// 用户权限枚举 /// public enum UserPermission { /// - /// [TODO:description] + /// 管理员权限,可以管理用户和实验板 /// Admin, /// - /// [TODO:description] + /// 普通用户权限,只能使用实验板 /// Normal, } @@ -81,41 +81,41 @@ public class Board public required string BoardName { get; set; } /// - /// [TODO:description] + /// FPGA 板子的IP地址 /// [NotNull] public required string IpAddr { get; set; } /// - /// [TODO:description] + /// FPGA 板子的通信端口 /// [NotNull] public required int Port { get; set; } /// - /// [TODO:description] + /// FPGA 板子的当前状态 /// [NotNull] public required BoardStatus Status { get; set; } /// - /// [TODO:description] + /// FPGA 板子的固件版本号 /// [NotNull] public string FirmVersion { get; set; } = "1.0.0"; /// - /// [TODO:description] + /// FPGA 板子状态枚举 /// public enum BoardStatus { /// - /// [TODO:description] + /// 繁忙状态,正在被用户使用 /// Busy, /// - /// [TODO:description] + /// 可用状态,可以被分配给用户 /// Available, } @@ -140,6 +140,7 @@ public class AppDataConnection : DataConnection { if (!Path.Exists(DATABASE_FILEPATH)) { + logger.Info($"数据库文件不存在,正在创建新数据库: {DATABASE_FILEPATH}"); LinqToDB.DataProvider.SQLite.SQLiteTools.CreateDatabase(DATABASE_FILEPATH); this.CreateAllTables(); var user = new User() @@ -150,6 +151,11 @@ public class AppDataConnection : DataConnection Permission = Database.User.UserPermission.Admin, }; this.Insert(user); + logger.Info("默认管理员用户已创建"); + } + else + { + logger.Info($"数据库连接已建立: {DATABASE_FILEPATH}"); } } @@ -159,8 +165,10 @@ public class AppDataConnection : DataConnection /// public void CreateAllTables() { + logger.Info("正在创建数据库表..."); this.CreateTable(); this.CreateTable(); + logger.Info("数据库表创建完成"); } /// @@ -168,16 +176,18 @@ public class AppDataConnection : DataConnection /// public void DropAllTables() { + logger.Warn("正在删除所有数据库表..."); this.DropTable(); this.DropTable(); + logger.Warn("所有数据库表已删除"); } /// /// 添加一个新的用户到数据库 /// /// 用户的名称 - /// [TODO:parameter] - /// [TODO:parameter] + /// 用户的电子邮箱地址 + /// 用户的密码 /// 插入的记录数 public int AddUser(string name, string email, string password) { @@ -188,63 +198,67 @@ public class AppDataConnection : DataConnection Password = password, Permission = Database.User.UserPermission.Normal, }; - return this.Insert(user); + var result = this.Insert(user); + logger.Info($"新用户已添加: {name} ({email})"); + return result; } /// - /// [TODO:description] + /// 根据用户名获取用户信息 /// - /// [TODO:parameter] - /// [TODO:return] + /// 用户名 + /// 包含用户信息的结果,如果未找到或出错则返回相应状态 public Result> GetUserByName(string name) { var user = this.User.Where((user) => user.Name == name).ToArray(); if (user.Length > 1) { - logger.Error($"TODO"); - return new(new Exception($"TODO")); + logger.Error($"数据库中存在多个同名用户: {name}"); + return new(new Exception($"数据库中存在多个同名用户: {name}")); } if (user.Length == 0) { - logger.Info($"TODO"); + logger.Info($"未找到用户: {name}"); return new(Optional.None); } + logger.Debug($"成功获取用户信息: {name}"); return new(user[0]); } /// - /// [TODO:description] + /// 根据电子邮箱获取用户信息 /// - /// [TODO:parameter] - /// [TODO:return] + /// 用户的电子邮箱地址 + /// 包含用户信息的结果,如果未找到或出错则返回相应状态 public Result> GetUserByEMail(string email) { var user = this.User.Where((user) => user.EMail == email).ToArray(); if (user.Length > 1) { - logger.Error($"TODO"); - return new(new Exception($"TODO")); + logger.Error($"数据库中存在多个相同邮箱的用户: {email}"); + return new(new Exception($"数据库中存在多个相同邮箱的用户: {email}")); } if (user.Length == 0) { - logger.Info($"TODO"); + logger.Info($"未找到邮箱对应的用户: {email}"); return new(Optional.None); } + logger.Debug($"成功获取用户信息: {email}"); return new(user[0]); } /// - /// [TODO:description] + /// 验证用户密码 /// - /// [TODO:parameter] - /// [TODO:parameter] - /// [TODO:return] + /// 用户名 + /// 用户密码 + /// 如果密码正确返回用户信息,否则返回空 public Result> CheckUserPassword(string name, string password) { var ret = this.GetUserByName(name); @@ -256,16 +270,40 @@ public class AppDataConnection : DataConnection var user = ret.Value.Value; - if (user.Password == password) return new(user); - else return new(Optional.None); + if (user.Password == password) + { + logger.Info($"用户 {name} 密码验证成功"); + return new(user); + } + else + { + logger.Warn($"用户 {name} 密码验证失败"); + return new(Optional.None); + } + } + + /// + /// 绑定用户与实验板 + /// + /// 用户的唯一标识符 + /// 实验板的唯一标识符 + /// 更新的记录数 + public int BindUserToBoard(Guid userId, Guid boardId) + { + var result = this.User + .Where(u => u.ID == userId) + .Set(u => u.BoardID, boardId) + .Update(); + logger.Info($"用户 {userId} 已绑定到实验板 {boardId}"); + return result; } /// /// 添加一块新的 FPGA 板子到数据库 /// /// FPGA 板子的名称 - /// [TODO:Param] - /// [TODO:Param] + /// FPGA 板子的IP地址 + /// FPGA 板子的通信端口 /// 插入的记录数 public int AddBoard(string name, string ipAddr, int port) { @@ -276,51 +314,59 @@ public class AppDataConnection : DataConnection Port = port, Status = Database.Board.BoardStatus.Available, }; - return this.Insert(board); + var result = this.Insert(board); + logger.Info($"新实验板已添加: {name} ({ipAddr}:{port})"); + return result; } /// - /// [TODO:description] + /// 根据名称删除实验板 /// - /// [TODO:parameter] - /// [TODO:return] + /// 实验板的名称 + /// 删除的记录数 public int DeleteBoardByName(string name) { - return this.Board.Where(board => board.BoardName == name).Delete(); + var result = this.Board.Where(board => board.BoardName == name).Delete(); + logger.Info($"实验板已删除: {name},删除记录数: {result}"); + return result; } /// - /// [TODO:description] + /// 根据ID删除实验板 /// - /// [TODO:parameter] - /// [TODO:return] + /// 实验板的唯一标识符 + /// 删除的记录数 public int DeleteBoardByID(Guid id) { - return this.Board.Where(board => board.ID == id).Delete(); + var result = this.Board.Where(board => board.ID == id).Delete(); + logger.Info($"实验板已删除: {id},删除记录数: {result}"); + return result; } /// - /// [TODO:description] + /// 获取所有实验板信息 /// - /// [TODO:return] + /// 所有实验板的数组 public Board[] GetAllBoard() { - return this.Board.ToArray(); + var boards = this.Board.ToArray(); + logger.Debug($"获取所有实验板,共 {boards.Length} 块"); + return boards; } /// - /// [TODO:description] + /// 获取一块可用的实验板并将其状态设置为繁忙 /// - /// [TODO:return] + /// 可用的实验板,如果没有可用的板子则返回空 public Optional GetAvailableBoard() { var boards = this.Board.Where( (board) => board.Status == Database.Board.BoardStatus.Available ).ToArray(); - if (boards.Length < 0) + if (boards.Length == 0) { - logger.Warn($"TODO"); + logger.Warn("没有可用的实验板"); return new(null); } else @@ -331,6 +377,7 @@ public class AppDataConnection : DataConnection .Where(target => target.ID == board.ID) .Set(target => target.Status, board.Status) .Update(); + logger.Info($"实验板 {board.BoardName} ({board.ID}) 已分配,状态更新为繁忙"); return new(board); } } diff --git a/src/APIClient.ts b/src/APIClient.ts index 55c32ed..00619e8 100644 --- a/src/APIClient.ts +++ b/src/APIClient.ts @@ -649,6 +649,54 @@ export class DataClient { return Promise.resolve(null as any); } + /** + * 获取一个空闲的实验板(普通用户权限) + */ + getAvailableBoard(): Promise { + let url_ = this.baseUrl + "/api/Data/GetAvailableBoard"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processGetAvailableBoard(_response); + }); + } + + protected processGetAvailableBoard(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = Board.fromJS(resultData200); + return result200; + }); + } else if (status === 404) { + return response.text().then((_responseText) => { + let result404: any = null; + let resultData404 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result404 = ProblemDetails.fromJS(resultData404); + return throwException("A server side error occurred.", status, _responseText, _headers, result404); + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + return throwException("A server side error occurred.", status, _responseText, _headers); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + /** * 新增板子(管理员权限) * @param name (optional) @@ -2721,13 +2769,13 @@ export class Board implements IBoard { id!: string; /** FPGA 板子的名称 */ boardName!: string; - /** [TODO:description] */ + /** FPGA 板子的IP地址 */ ipAddr!: string; - /** [TODO:description] */ + /** FPGA 板子的通信端口 */ port!: number; - /** [TODO:description] */ + /** FPGA 板子的当前状态 */ status!: BoardStatus; - /** [TODO:description] */ + /** FPGA 板子的固件版本号 */ firmVersion!: string; constructor(data?: IBoard) { @@ -2775,17 +2823,17 @@ export interface IBoard { id: string; /** FPGA 板子的名称 */ boardName: string; - /** [TODO:description] */ + /** FPGA 板子的IP地址 */ ipAddr: string; - /** [TODO:description] */ + /** FPGA 板子的通信端口 */ port: number; - /** [TODO:description] */ + /** FPGA 板子的当前状态 */ status: BoardStatus; - /** [TODO:description] */ + /** FPGA 板子的固件版本号 */ firmVersion: string; } -/** [TODO:description] */ +/** FPGA 板子状态枚举 */ export enum BoardStatus { Busy = 0, Available = 1, diff --git a/src/views/User/BoardManager.ts b/src/utils/BoardManager.ts similarity index 100% rename from src/views/User/BoardManager.ts rename to src/utils/BoardManager.ts diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 482f5a4..1efc189 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -34,55 +34,9 @@ to="/project" class="btn btn-primary text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1" > - - - - + 进入工程界面 - - - - - - - 登录 - - - - - - - - 用户中心 -
import "@/router"; import TutorialCarousel from "@/components/TutorialCarousel.vue"; +import { BookOpen } from "lucide-vue-next";