diff --git a/ThuInfoWeb/Bots/FeedbackNoticeBot.cs b/ThuInfoWeb/Bots/FeedbackNoticeBot.cs index 755e4b4..b204d57 100644 --- a/ThuInfoWeb/Bots/FeedbackNoticeBot.cs +++ b/ThuInfoWeb/Bots/FeedbackNoticeBot.cs @@ -1,67 +1,50 @@ -using System.Buffers.Text; -using System.Security.Cryptography; +using System.Security.Cryptography; using System.Text; using System.Text.Json; -namespace ThuInfoWeb.Bots +namespace ThuInfoWeb.Bots; + +public class FeedbackNoticeBot(IConfiguration configuration) { - public class FeedbackNoticeBot - { - private readonly string _url; - private readonly string _secret; - private readonly HttpClient _httpClient; - private readonly bool _internalNetworkMode; + private readonly HttpClient _httpClient = new(); + private readonly bool _internalNetworkMode = bool.Parse(configuration["InternalNetworkMode"] ?? "false"); + private readonly string _secret = configuration["FeishuBots:FeedbackNoticeBot:Secret"] ?? ""; + private readonly string _url = configuration["FeishuBots:FeedbackNoticeBot:Url"] ?? ""; - public FeedbackNoticeBot(IConfiguration configuration) - { - this._url = configuration["FeishuBots:FeedbackNoticeBot:Url"]; - this._secret = configuration["FeishuBots:FeedbackNoticeBot:Secret"]; - this._internalNetworkMode = bool.Parse(configuration["InternalNetworkMode"]); - this._httpClient = new HttpClient(); - } + private string GetSign(long timestamp) + { + var str = $"{timestamp}\n{_secret}"; + using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(str)); + var code = hmac.ComputeHash([]); + var sign = Convert.ToBase64String(code); + return sign; + } - private string GetSign(long timestamp) + public async Task PushNoticeAsync(string content) + { + if (_internalNetworkMode) { - var str = $"{timestamp}\n{_secret}"; - using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(str)); - var code = hmac.ComputeHash(new byte[0]); - var sign = Convert.ToBase64String(code); - return sign; + var resp = await _httpClient.PostAsync("https://stu.cs.tsinghua.edu.cn/thuinfo/botnotice", + JsonContent.Create(new { Content = content, Secret = _secret })); + resp.EnsureSuccessStatusCode(); } - - public async Task PushNoticeAsync(string content) + else { - if (_internalNetworkMode) - { - var resp = await _httpClient.PostAsync("https://stu.cs.tsinghua.edu.cn/thuinfo/botnotice", JsonContent.Create(new - { - Content = content, - Secret = _secret - })); - resp.EnsureSuccessStatusCode(); - } - else - { - var ts = DateTimeOffset.Now.ToUnixTimeSeconds(); - var resp = await _httpClient.PostAsync(_url, JsonContent.Create(new + var ts = DateTimeOffset.Now.ToUnixTimeSeconds(); + var resp = await _httpClient.PostAsync(_url, + JsonContent.Create(new { timestamp = ts.ToString(), sign = GetSign(ts), msg_type = "text", - content = new - { - text = content - } + content = new { text = content } })); - var json = await resp.Content.ReadAsStringAsync(); - var parsed = JsonDocument.Parse(json); - if (!parsed.RootElement.TryGetProperty("StatusCode", out var code)) - throw new Exception("Send error"); - else if (code.GetInt32() != 0) - throw new Exception("Send error"); - else - return; - } + var json = await resp.Content.ReadAsStringAsync(); + var parsed = JsonDocument.Parse(json); + if (!parsed.RootElement.TryGetProperty("StatusCode", out var code)) + throw new Exception("Send error"); + if (code.GetInt32() != 0) + throw new Exception("Send error"); } } -} \ No newline at end of file +} diff --git a/ThuInfoWeb/Controllers/ApiController.cs b/ThuInfoWeb/Controllers/ApiController.cs index d5b864e..7bfb8ee 100644 --- a/ThuInfoWeb/Controllers/ApiController.cs +++ b/ThuInfoWeb/Controllers/ApiController.cs @@ -1,155 +1,149 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using ThuInfoWeb.Bots; using ThuInfoWeb.DBModels; using ThuInfoWeb.Dtos; -namespace ThuInfoWeb.Controllers +namespace ThuInfoWeb.Controllers; + +/// +/// The controller for RESTApi +/// +[Route("[controller]")] +[ApiController] +public class ApiController(Data data, VersionManager versionManager, FeedbackNoticeBot feedbackNoticeBot) + : ControllerBase { /// - /// The controller for RESTApi + /// Get announce, get the latest announce simply by no query string(just get /api/announce). If needed, you should only + /// enter id or page at one time. /// - [Route("[controller]")] - [ApiController] - public class ApiController : ControllerBase + /// if entered, this will return single value + /// if entered, this will return up to 5 values in an array. + /// In json format. + [Route("Announce")] + public async Task Announce([FromQuery] int? id, [FromQuery] int? page) { - private readonly Data _data; - private readonly VersionManager _versionManager; - private readonly FeedbackNoticeBot _feedbackNoticeBot; - - public ApiController(Data data, VersionManager versionManager, FeedbackNoticeBot feedbackNoticeBot) + if (page <= 0) + return BadRequest("page必须是正整数"); + if (page is not null) { - this._data = data; - this._versionManager = versionManager; - this._feedbackNoticeBot = feedbackNoticeBot; + var a = await data.GetActiveAnnouncesAsync((int)page, 5); + return Ok(a); } - - /// - /// Get announce, get the latest announce simply by no query string(just get /api/announce). If needed, you should only enter id or page at one time. - /// - /// if entered, this will return single value - /// if entered, this will return up to 5 values in an array. - /// In json format. - [Route("Announce")] - public async Task Announce([FromQuery] int? id, [FromQuery] int? page) + else { - if (page is not null && page <= 0) - return BadRequest("page必须是正整数"); - if (page is not null) - { - var a = await _data.GetActiveAnnouncesAsync(page ?? 1, 5); - return Ok(a); - } - else - { - var a = await _data.GetActiveAnnounceAsync(id); - return Ok(a); - } + var a = await data.GetActiveAnnounceAsync(id); + return Ok(a); } + } - /// - /// Create a feedback - /// - /// a json, has content, appversion, os, nickname(optional) - /// - [Route("Feedback"), HttpPost] - public async Task Feedback(FeedbackDto dto) + /// + /// Create a feedback + /// + /// a json, has content, appversion, os, nickname(optional) + /// + [Route("Feedback")] + [HttpPost] + public async Task Feedback(FeedbackDto dto) + { + var feedback = new Feedback { - var feedback = new Feedback() - { - AppVersion = dto.AppVersion, - Content = dto.Content, - CreatedTime = DateTime.Now, - OS = dto.OS.ToLower(), - Contact = dto.Contact, - PhoneModel = dto.PhoneModel - }; - var result = await _data.CreateFeedbackAsync(feedback); - if (result != 1) return BadRequest(); - else - { - _ = _feedbackNoticeBot.PushNoticeAsync( - $"收到新反馈\n{dto.Content}\n请前往http://app.cs.tsinghua.edu.cn/Home/Feedback回复"); - return Created("Api/Feedback", null); - } - } - - [Route("RepliedFeedback")] - public async Task RepliedFeedback() + AppVersion = dto.AppVersion, + Content = dto.Content, + CreatedTime = DateTime.Now, + OS = dto.OS.ToLower(), + Contact = dto.Contact, + PhoneModel = dto.PhoneModel + }; + var result = await data.CreateFeedbackAsync(feedback); + if (result != 1) { - return Ok((await _data.GetAllRepliedFeedbacksAsync()) - .Select(x => new - { - content = x.Content, - reply = x.Reply, - replierName = x.ReplierName ?? "", - repliedTime = x.RepliedTime - }).ToList()); + return BadRequest(); } - /// - /// Get the url content of Wechat group QRCode. - /// - /// The url string, NOT in json format. - [Route("QRCode")] - public async Task QRCode() - { - return Ok((await _data.GetMiscAsync()).QrCodeContent); - } + _ = feedbackNoticeBot.PushNoticeAsync( + $"收到新反馈\n{dto.Content}\n请前往http://app.cs.tsinghua.edu.cn/Home/Feedback回复"); + return Created("Api/Feedback", null); + } - /// - /// Redirect to the url ok APK. - /// - /// - [Route("Apk")] - public async Task Apk() - { - // when start for the first time, if the apkurl is null or empty, this will generate an exception, so set an apkurl value as soon as possible. - return Redirect((await _data.GetMiscAsync())?.ApkUrl); - } + [Route("RepliedFeedback")] + public async Task RepliedFeedback() + { + return Ok((await data.GetAllRepliedFeedbacksAsync()) + .Select(x => new + { + content = x.Content, reply = x.Reply, replierName = x.ReplierName, repliedTime = x.RepliedTime + }).ToList()); + } - [Route("Socket")] - public async Task Socket([FromQuery] int? sectionId) + /// + /// Get the url content of Wechat group QRCode. + /// + /// The url string, NOT in json format. + [Route("QRCode")] + public async Task QRCode() + { + return Ok((await data.GetMiscAsync())?.QrCodeContent ?? ""); + } + + /// + /// Redirect to the url ok APK. + /// + /// + [Route("Apk")] + public async Task Apk() + { + // when start for the first time, if the apkurl is null or empty, this will generate an exception, so set an apkurl value as soon as possible. + return Redirect((await data.GetMiscAsync())?.ApkUrl ?? ""); + } + + [Route("Socket")] + public async Task Socket([FromQuery] int? sectionId) + { + if (sectionId is null) + return Ok(new List()); + + return Ok((await data.GetSocketsAsync((int)sectionId)).Select(x => new SocketDto { - if (sectionId is null) - return Ok(new List()); + CreatedTime = x.CreatedTime, + SeatId = x.SeatId, + SectionId = x.SectionId, + UpdatedTime = x.UpdatedTime, + Status = Parse(x.Status) + }).ToList()); - static string parse(Socket.SocketStatus status) => status switch + static string Parse(Socket.SocketStatus status) + { + return status switch { DBModels.Socket.SocketStatus.Available => "available", DBModels.Socket.SocketStatus.Unavailable => "unavailable", - DBModels.Socket.SocketStatus.Unknown => "unknown" + _ => "unknown" }; - - return Ok((await _data.GetSocketsAsync(sectionId ?? 0)).Select(x => new SocketDto() - { - CreatedTime = x.CreatedTime, - SeatId = x.SeatId, - SectionId = x.SectionId, - UpdatedTime = x.UpdatedTime, - Status = parse(x.Status) - }).ToList()); } + } - [HttpPost, Route("Socket")] - public async Task Socket(SocketDto dto) - { - var result = await _data.UpdateSocketAsync(dto.SeatId ?? 0, dto.IsAvailable ?? false); - if (result != 1) return BadRequest(); - else return Ok(); - } + [HttpPost] + [Route("Socket")] + public async Task Socket(SocketDto dto) + { + var result = await data.UpdateSocketAsync(dto.SeatId ?? 0, dto.IsAvailable ?? false); + if (result != 1) + return BadRequest(); + return Ok(); + } - [Route("Version/{os}")] - public IActionResult Version([FromRoute] string os) - { - if (os.ToLower() == "android") return Ok(_versionManager.GetCurrentVersion(VersionManager.OS.Android)); - else return Ok(_versionManager.GetCurrentVersion(VersionManager.OS.IOS)); - } + [Route("Version/{os}")] + public IActionResult Version([FromRoute] string os) + { + return Ok(os.Equals("android", StringComparison.CurrentCultureIgnoreCase) + ? versionManager.GetCurrentVersion(VersionManager.OS.Android) + : versionManager.GetCurrentVersion(VersionManager.OS.IOS)); + } - [Route("CardIVersion")] - public async Task CardIVersion() - { - return Ok(new { Version = (await _data.GetMiscAsync()).CardIVersion }); - } + [Route("CardIVersion")] + public async Task CardIVersion() + { + return Ok(new { Version = (await data.GetMiscAsync())?.CardIVersion ?? -1 }); } -} \ No newline at end of file +} diff --git a/ThuInfoWeb/Controllers/HomeController.cs b/ThuInfoWeb/Controllers/HomeController.cs index 09a8653..3fb4ca6 100644 --- a/ThuInfoWeb/Controllers/HomeController.cs +++ b/ThuInfoWeb/Controllers/HomeController.cs @@ -1,286 +1,299 @@ -using Microsoft.AspNetCore.Authorization; +using System.Diagnostics; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using System.Diagnostics; -using System.Text.RegularExpressions; using ThuInfoWeb.DBModels; using ThuInfoWeb.Models; -namespace ThuInfoWeb.Controllers +namespace ThuInfoWeb.Controllers; + +public class HomeController(ILogger logger, Data data, UserManager userManager, + VersionManager versionManager) + : Controller { - public partial class HomeController : Controller + private readonly ILogger _logger = logger; + + public IActionResult Register() { - private readonly ILogger _logger; - private readonly Data _data; - private readonly UserManager _userManager; - private readonly VersionManager _versionManager; + return View(); + } - [GeneratedRegex(@"^\d+\.\d+\.\d+$")] - private static partial Regex VersionRegex(); + [HttpPost] + public IActionResult Register(RegisterViewModel vm) + { + if (!ModelState.IsValid) + return View(vm); - public HomeController(ILogger logger, Data data, UserManager userManager, VersionManager versionManager) - { - _logger = logger; - _data = data; - _userManager = userManager; - _versionManager = versionManager; - } - public IActionResult Register() - { - return View(); - } - [HttpPost] - public async Task Register(RegisterViewModel vm) - { - if (!ModelState.IsValid) return View(vm); + // Prohibit registration + ModelState.AddModelError(nameof(vm.Name), "禁止注册新用户"); + return View(vm); - // Prohibit registration - ModelState.AddModelError(nameof(vm.Name), "禁止注册新用户"); - return View(vm); + // if (await _data.CheckUserAsync(vm.Name)) + // { + // ModelState.AddModelError(nameof(vm.Name), "用户名已被注册"); + // return View(vm); + // } + // var user = new User() + // { + // Name = vm.Name, + // PasswordHash = vm.Password.ToSHA256Hex(), + // CreatedTime = DateTime.Now, + // IsAdmin = false + // }; + // var result = await _data.CreateUserAsync(user); + // if (result == 1) + // { + // await _userManager.DoLoginAsync(vm.Name, false); + // return RedirectToAction("Index"); + // } + // else + // { + // ModelState.AddModelError(nameof(vm.Name), "发生未知错误"); + // return View(vm); + // } + } - // if (await _data.CheckUserAsync(vm.Name)) - // { - // ModelState.AddModelError(nameof(vm.Name), "用户名已被注册"); - // return View(vm); - // } - // var user = new User() - // { - // Name = vm.Name, - // PasswordHash = vm.Password.ToSHA256Hex(), - // CreatedTime = DateTime.Now, - // IsAdmin = false - // }; - // var result = await _data.CreateUserAsync(user); - // if (result == 1) - // { - // await _userManager.DoLoginAsync(vm.Name, false); - // return RedirectToAction("Index"); - // } - // else - // { - // ModelState.AddModelError(nameof(vm.Name), "发生未知错误"); - // return View(vm); - // } - } - public IActionResult Login() - { - return View(); - } + public IActionResult Login() + { + return View(); + } - [HttpPost] - public async Task Login(LoginViewModel vm) + [HttpPost] + public async Task Login(LoginViewModel vm) + { + if (!ModelState.IsValid) + return View(vm); + // get the user and check if the password is correct + var user = vm.Name != null ? await data.GetUserAsync(vm.Name) : null; + if (user is null || vm.Password?.ToSHA256Hex() != user.PasswordHash) { - if (!ModelState.IsValid) return View(vm); - // get the user and check if the password is correct - var user = await _data.GetUserAsync(vm.Name); - if (user is null || vm.Password.ToSHA256Hex() != user.PasswordHash) - { - ModelState.AddModelError(nameof(vm.Name), "用户名或密码错误"); - ModelState.AddModelError(nameof(vm.Password), "用户名或密码错误"); - return View(vm); - } - await _userManager.DoLoginAsync(user.Name, user.IsAdmin); - return RedirectToAction("Index"); + ModelState.AddModelError(nameof(vm.Name), "用户名或密码错误"); + ModelState.AddModelError(nameof(vm.Password), "用户名或密码错误"); + return View(vm); } - [Authorize(Roles = "admin,guest")] - public async Task Logout() - { - await _userManager.DoLogoutAsync(); - return RedirectToAction("Login"); - } - [Authorize(Roles = "admin,guest")] - public IActionResult ChangePassword() => View(); - [HttpPost, Authorize(Roles = "admin,guest")] - public async Task ChangePassword(ChangePasswordViewModel vm) - { - if (!ModelState.IsValid) return View(vm); - if (HttpContext.User.Identity.Name != vm.Name) BadRequest(); - if (vm.OldPassword.ToSHA256Hex() != (await _data.GetUserAsync(HttpContext.User.Identity.Name)).PasswordHash) - { - ModelState.AddModelError(nameof(vm.OldPassword), "旧密码错误"); - return View(vm); - } - var result = await _data.ChangeUserPasswordAsync(HttpContext.User.Identity.Name, vm.NewPassword.ToSHA256Hex()); - if (result != 1) - { - ModelState.AddModelError(nameof(vm.NewPassword), "发生未知错误"); - return View(vm); - } - else - { - await _userManager.DoLogoutAsync(); - return RedirectToAction(nameof(Login)); - } - } + await userManager.DoLoginAsync(user.Name, user.IsAdmin); + return RedirectToAction("Index"); + } - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() - { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); - } + [Authorize(Roles = "admin,guest")] + public async Task Logout() + { + await userManager.DoLogoutAsync(); + return RedirectToAction("Login"); + } + + [Authorize(Roles = "admin,guest")] + public IActionResult ChangePassword() + { + return View(); + } - [Authorize(Roles = "admin,guest")] - public IActionResult Index() + [HttpPost] + [Authorize(Roles = "admin,guest")] + public async Task ChangePassword(ChangePasswordViewModel vm) + { + if (!ModelState.IsValid) + return View(vm); + if (HttpContext.User.Identity!.Name != vm.Name) + BadRequest(); + if (vm.OldPassword?.ToSHA256Hex() != (await data.GetUserAsync(HttpContext.User.Identity!.Name!))!.PasswordHash) { - return View(); + ModelState.AddModelError(nameof(vm.OldPassword), "旧密码错误"); + return View(vm); } - [Authorize(Roles = "admin")] - public async Task Announce([FromQuery] int page = 1) + var result = await data.ChangeUserPasswordAsync(HttpContext.User.Identity.Name!, vm.NewPassword!.ToSHA256Hex()); + if (result != 1) { - ViewData["page"] = page; - var list = await _data.GetAnnouncesAsync(page, 10); - return View(list.Select(a => new AnnounceViewModel() - { - Id = a.Id, - Content = a.Content, - Title = a.Title, - Author = a.Author, - CreatedTime = a.CreatedTime, - IsActive = a.IsActive, - VisibleNotAfter = a.VisibleNotAfter, - VisibleExact = a.VisibleExact - }).ToList()); + ModelState.AddModelError(nameof(vm.NewPassword), "发生未知错误"); + return View(vm); } - [HttpPost, Authorize(Roles = "admin")] - public async Task CreateAnnounce(AnnounceViewModel vm) - { - if (vm.Title is null || vm.Content is null) return BadRequest("标题或内容为空"); - var visibleNotAfter = vm.VisibleNotAfter?.Trim() ?? "9.9.9"; - var visibleExact = vm.VisibleExact ?? ""; - - if (!VersionRegex().IsMatch(visibleNotAfter)) - { - return BadRequest("\"在不晚于以下版本生效\"中的版本号格式错误"); - } - - var visibleExactList = visibleExact.Split(',') - .Select(x => x.Trim()) - .Where(x => !string.IsNullOrWhiteSpace(x)) - .ToList(); - - if (visibleExactList.Any(x => !VersionRegex().IsMatch(x))) - { - return BadRequest("\"在以下版本生效\"中的版本号格式错误"); - } - - visibleExact = string.Join(',', visibleExactList); + await userManager.DoLogoutAsync(); + return RedirectToAction(nameof(Login)); + } - var user = HttpContext.User.Identity!.Name!; - var a = new Announce - { - Title = vm.Title, - Content = vm.Content, - Author = user, - CreatedTime = DateTime.Now, - IsActive = vm.IsActive, - VisibleNotAfter = visibleNotAfter, - VisibleExact = visibleExact - }; - var result = await _data.CreateAnnounceAsync(a); - if (result != 1) return BadRequest(ModelState); - else return CreatedAtAction(nameof(Announce), null); - } - [Authorize(Roles = "admin")] - public async Task ChangeAnnounceStatus([FromRoute] int id, [FromQuery] int returnpage) - { - var a = await _data.GetAnnounceAsync(id); - if (a is null) return BadRequest("找不到对应公告"); - var result = await _data.UpdateAnnounceStatusAsync(id, !a.IsActive); - if (result != 1) return BadRequest(); - else return RedirectToAction(nameof(Announce), new { page = returnpage == 0 ? 1 : returnpage }); - } - [Authorize(Roles = "admin")] - public async Task DeleteAnnounce([FromRoute] int id, [FromQuery] int returnpage) - { - var result = await _data.DeleteAnnounceAsync(id); - if (result != 1) return NoContent(); - else return RedirectToAction(nameof(Announce), new { page = returnpage }); - } - [Authorize(Roles = "admin")] - public async Task Feedback([FromQuery] int page = 1) - { - var list = (await _data.GetFeedbacksAsync(page, 10)).Select(x => - new FeedbackViewModel() - { - AppVersion = x.AppVersion, - Contact = x.Contact, - Content = x.Content, - CreatedTime = x.CreatedTime, - Id = x.Id, - OS = x.OS, - PhoneModel = x.PhoneModel, - Reply = x.Reply, - ReplyerName = x.ReplierName, - RepliedTime = x.RepliedTime - }).ToList(); - ViewData["page"] = page; - return View(list); - } - [Authorize(Roles = "admin")] - public async Task DeleteFeedback([FromRoute] int id, [FromQuery] int returnpage = 1) - { - var result = await _data.DeleteFeedbackAsync(id); - if (result != 1) return NoContent(); - else return RedirectToAction(nameof(Feedback), new { page = returnpage }); - } - [HttpPost, Authorize(Roles = "admin")] - public async Task ReplyFeedback([FromForm] int id, [FromForm] string reply) - { - if (string.IsNullOrWhiteSpace(reply)) - { - return BadRequest("回复不能为空"); - } - var user = HttpContext.User.Identity.Name; - var result = await _data.ReplyFeedbackAsync(id, reply, user); - if (result != 1) return BadRequest("未知错误"); - else return Ok(); - } - [Authorize(Roles = "admin")] - public async Task Misc() + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() + { + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } + + [Authorize(Roles = "admin,guest")] + public IActionResult Index() + { + return View(); + } + + [Authorize(Roles = "admin")] + public async Task Announce([FromQuery] int page = 1) + { + ViewData["page"] = page; + var list = await data.GetAnnouncesAsync(page, 10); + return View(list.Select(a => new AnnounceViewModel { - var misc = await _data.GetMiscAsync(); - return View(new MiscViewModel() - { - ApkUrl = misc.ApkUrl, - QrCodeContent = misc.QrCodeContent, - CardIVersion = misc.CardIVersion - }); - } - [HttpPost, Authorize(Roles = "admin")] - public async Task Misc(MiscViewModel vm) + Id = a.Id, + Content = a.Content, + Title = a.Title, + Author = a.Author, + CreatedTime = a.CreatedTime, + IsActive = a.IsActive, + VisibleNotAfter = a.VisibleNotAfter, + VisibleExact = a.VisibleExact + }).ToList()); + } + + [HttpPost] + [Authorize(Roles = "admin")] + public async Task CreateAnnounce(AnnounceViewModel vm) + { + if (vm.Title is null || vm.Content is null) + return BadRequest("标题或内容为空"); + var visibleNotAfter = vm.VisibleNotAfter?.Trim() ?? "9.9.9"; + var visibleExact = vm.VisibleExact ?? ""; + + if (!visibleNotAfter.IsValidVersionNumber()) + return BadRequest("\"在不晚于以下版本生效\"中的版本号格式错误"); + + var visibleExactList = visibleExact.Split(',') + .Select(x => x.Trim()) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .ToList(); + + if (visibleExactList.Any(x => !x.IsValidVersionNumber())) + return BadRequest("\"在以下版本生效\"中的版本号格式错误"); + + visibleExact = string.Join(',', visibleExactList); + + var user = HttpContext.User.Identity!.Name!; + var a = new Announce { - if (!ModelState.IsValid) return View(vm); - var misc = new Misc() + Title = vm.Title, + Content = vm.Content, + Author = user, + CreatedTime = DateTime.Now, + IsActive = vm.IsActive, + VisibleNotAfter = visibleNotAfter, + VisibleExact = visibleExact + }; + var result = await data.CreateAnnounceAsync(a); + if (result != 1) + return BadRequest(ModelState); + return CreatedAtAction(nameof(Announce), null); + } + + [Authorize(Roles = "admin")] + public async Task ChangeAnnounceStatus([FromRoute] int id, [FromQuery] int returnpage) + { + var a = await data.GetAnnounceAsync(id); + if (a is null) + return BadRequest("找不到对应公告"); + var result = await data.UpdateAnnounceStatusAsync(id, !a.IsActive); + if (result != 1) + return BadRequest(); + return RedirectToAction(nameof(Announce), new { page = returnpage == 0 ? 1 : returnpage }); + } + + [Authorize(Roles = "admin")] + public async Task DeleteAnnounce([FromRoute] int id, [FromQuery] int returnpage) + { + var result = await data.DeleteAnnounceAsync(id); + if (result != 1) + return NoContent(); + return RedirectToAction(nameof(Announce), new { page = returnpage }); + } + + [Authorize(Roles = "admin")] + public async Task Feedback([FromQuery] int page = 1) + { + var list = (await data.GetFeedbacksAsync(page, 10)).Select(x => + new FeedbackViewModel { - ApkUrl = vm.ApkUrl, - QrCodeContent = vm.QrCodeContent, - CardIVersion = vm.CardIVersion - }; - var result = await _data.UpdateMiscAsync(misc); - if (result != 1) return BadRequest(); - else return RedirectToAction(nameof(Misc)); - } - [Authorize(Roles = "admin"), Route("Home/CheckUpdate/{os}")] - public IActionResult CheckUpdate([FromRoute] string os) + AppVersion = x.AppVersion, + Contact = x.Contact, + Content = x.Content, + CreatedTime = x.CreatedTime, + Id = x.Id, + OS = x.OS, + PhoneModel = x.PhoneModel, + Reply = x.Reply, + ReplierName = x.ReplierName, + RepliedTime = x.RepliedTime + }).ToList(); + ViewData["page"] = page; + return View(list); + } + + [Authorize(Roles = "admin")] + public async Task DeleteFeedback([FromRoute] int id, [FromQuery] int returnpage = 1) + { + var result = await data.DeleteFeedbackAsync(id); + if (result != 1) + return NoContent(); + return RedirectToAction(nameof(Feedback), new { page = returnpage }); + } + + [HttpPost] + [Authorize(Roles = "admin")] + public async Task ReplyFeedback([FromForm] int id, [FromForm] string reply) + { + if (string.IsNullOrWhiteSpace(reply)) + return BadRequest("回复不能为空"); + var user = HttpContext.User.Identity!.Name!; + var result = await data.ReplyFeedbackAsync(id, reply, user); + if (result != 1) + return BadRequest("未知错误"); + return Ok(); + } + + [Authorize(Roles = "admin")] + public async Task Misc() + { + var misc = await data.GetMiscAsync() ?? new Misc(); + return View(new MiscViewModel { - if (!_versionManager.IsRunning) - _ = _versionManager.CheckUpdateAsync(os.ToLower() == "android" ? VersionManager.OS.Android : VersionManager.OS.IOS); - return RedirectToAction(nameof(Index)); - } - [Authorize(Roles = "admin")] - public IActionResult Stat() + ApkUrl = misc.ApkUrl, QrCodeContent = misc.QrCodeContent, CardIVersion = misc.CardIVersion + }); + } + + [HttpPost] + [Authorize(Roles = "admin")] + public async Task Misc(MiscViewModel vm) + { + if (!ModelState.IsValid) + return View(vm); + var misc = new Misc { - return View(); - } + ApkUrl = vm.ApkUrl ?? "", QrCodeContent = vm.QrCodeContent ?? "", CardIVersion = vm.CardIVersion + }; + var result = await data.UpdateMiscAsync(misc); + if (result != 1) + return BadRequest(); + return RedirectToAction(nameof(Misc)); + } + + [Authorize(Roles = "admin")] + [Route("Home/CheckUpdate/{os}")] + public IActionResult CheckUpdate([FromRoute] string os) + { + if (!versionManager.IsRunning) + _ = versionManager.CheckUpdateAsync(os.Equals("android", StringComparison.CurrentCultureIgnoreCase) + ? VersionManager.OS.Android + : VersionManager.OS.IOS); + return RedirectToAction(nameof(Index)); + } + + [Authorize(Roles = "admin")] + public IActionResult Stat() + { + return View(); + } #if DEBUG - [Route("Home/Exception")] - public IActionResult Exception() - { - throw new Exception("Generated exception in DEBUG build"); - } -#endif + [Route("Home/Exception")] + public IActionResult Exception() + { + throw new Exception("Generated exception in DEBUG build"); } -} \ No newline at end of file +#endif +} diff --git a/ThuInfoWeb/Controllers/StatController.cs b/ThuInfoWeb/Controllers/StatController.cs index b7f2ff8..6862d2e 100644 --- a/ThuInfoWeb/Controllers/StatController.cs +++ b/ThuInfoWeb/Controllers/StatController.cs @@ -2,61 +2,51 @@ using Microsoft.AspNetCore.Mvc; using ThuInfoWeb.DBModels; -namespace ThuInfoWeb.Controllers +namespace ThuInfoWeb.Controllers; + +[Route("[controller]/[action]")] +[ApiController] +public class StatController(Data data) : ControllerBase { - [Route("[controller]/[action]")] - [ApiController] - public class StatController : ControllerBase + [Route("{function:int}")] + public async Task Usage(int function) { - private readonly Data _data; - - public StatController(Data data) - { - _data = data; - } - - [Route("{function:int}")] - public async Task Usage(int function) - { - if (!Enum.IsDefined(typeof(Usage.FunctionType), function)) - return BadRequest("功能不存在"); - var usage = new Usage - { - Function = (Usage.FunctionType)function, - CreatedTime = DateTime.Now - }; - var result = await _data.CreateUsageAsync(usage); - if (result != 1) - return BadRequest(); - return Ok(); - } + if (!Enum.IsDefined(typeof(Usage.FunctionType), function)) + return BadRequest("功能不存在"); + var usage = new Usage { Function = (Usage.FunctionType)function, CreatedTime = DateTime.Now }; + var result = await data.CreateUsageAsync(usage); + if (result != 1) + return BadRequest(); + return Ok(); + } - [Route(""), Authorize(Roles = "admin")] - public async Task UsageData() - { - return Ok(await _data.GetUsageAsync()); - } + [Route("")] + [Authorize(Roles = "admin")] + public async Task UsageData() + { + return Ok(await data.GetUsageAsync()); + } - public async Task Startup() - { - var s = new Startup { CreatedTime = DateTime.Now }; - var result = await _data.CreateStartupAsync(s); - if (result != 1) - return BadRequest(); - return Ok(); - } + public async Task Startup() + { + var s = new Startup { CreatedTime = DateTime.Now }; + var result = await data.CreateStartupAsync(s); + if (result != 1) + return BadRequest(); + return Ok(); + } - [Route(""), Authorize(Roles = "admin")] - public async Task StartupData() - { - return Ok(await _data.GetStartupDataAsync()); - } + [Route("")] + [Authorize(Roles = "admin")] + public async Task StartupData() + { + return Ok(await data.GetStartupDataAsync()); + } #if DEBUG - public async Task GenStartupData() - { - await _data.GenStartupDataAsync(); - return Ok(); - } -#endif + public async Task GenStartupData() + { + await data.GenStartupDataAsync(); + return Ok(); } -} \ No newline at end of file +#endif +} diff --git a/ThuInfoWeb/DBModels/Announce.cs b/ThuInfoWeb/DBModels/Announce.cs index 405b3bf..bf8b97e 100644 --- a/ThuInfoWeb/DBModels/Announce.cs +++ b/ThuInfoWeb/DBModels/Announce.cs @@ -1,29 +1,32 @@ -using FreeSql.DataAnnotations; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; +using FreeSql.DataAnnotations; -namespace ThuInfoWeb.DBModels +namespace ThuInfoWeb.DBModels; + +public class Announce { - public class Announce - { - [Column(IsIdentity = true, IsPrimary = true)] - public int Id { get; set; } + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; init; } - public string Title { get; set; } + [Column(StringLength = 50, IsNullable = false)] + public string Title { get; init; } = string.Empty; - [Column(StringLength = -1)] - public string Content { get; set; } + [Column(StringLength = -1, IsNullable = false)] + public string Content { get; init; } = string.Empty; - public string Author { get; set; } + [Column(StringLength = 50, IsNullable = false)] + public string Author { get; init; } = string.Empty; - public DateTime CreatedTime { get; set; } + [Column(IsNullable = false)] + public DateTime CreatedTime { get; init; } - [JsonIgnore] - public bool IsActive { get; set; } + [JsonIgnore] + [Column(IsNullable = false)] + public bool IsActive { get; init; } - [Column(StringLength = 10, IsNullable = false)] - public string VisibleNotAfter { get; set; } + [Column(StringLength = 10, IsNullable = false)] + public string VisibleNotAfter { get; init; } = "9.9.9"; - [Column(StringLength = 30, IsNullable = false)] - public string VisibleExact { get; set; } - } -} \ No newline at end of file + [Column(StringLength = 30, IsNullable = false)] + public string VisibleExact { get; init; } = ""; +} diff --git a/ThuInfoWeb/DBModels/Feedback.cs b/ThuInfoWeb/DBModels/Feedback.cs index d54ac0d..518c78a 100644 --- a/ThuInfoWeb/DBModels/Feedback.cs +++ b/ThuInfoWeb/DBModels/Feedback.cs @@ -1,20 +1,36 @@ using FreeSql.DataAnnotations; -namespace ThuInfoWeb.DBModels +namespace ThuInfoWeb.DBModels; + +public class Feedback { - public class Feedback - { - [Column(IsPrimary = true, IsIdentity = true)] - public int Id { get; set; } - [Column(StringLength = -1)] - public string Content { get; set; } - public string Contact { get; set; } = string.Empty; - public DateTime CreatedTime { get; set; } - public string AppVersion { get; set; } - public string OS { get; set; } - public string PhoneModel { get; set; } - public string Reply { get; set; } = string.Empty; - public string ReplierName { get; set; } = string.Empty; - public DateTime? RepliedTime { get; set; } - } + [Column(IsPrimary = true, IsIdentity = true)] + public int Id { get; set; } + + [Column(StringLength = -1, IsNullable = false)] + public string Content { get; init; } = string.Empty; + + [Column(IsNullable = false)] + public string Contact { get; init; } = string.Empty; + + [Column(IsNullable = false)] + public DateTime CreatedTime { get; init; } + + [Column(IsNullable = false)] + public string AppVersion { get; init; } = "0.0.0"; + + [Column(IsNullable = false)] + public string OS { get; init; } = string.Empty; + + [Column(IsNullable = false)] + public string PhoneModel { get; init; } = string.Empty; + + [Column(IsNullable = false)] + public string Reply { get; init; } = string.Empty; + + [Column(IsNullable = false)] + public string ReplierName { get; init; } = string.Empty; + + [Column(IsNullable = false)] + public DateTime? RepliedTime { get; init; } } diff --git a/ThuInfoWeb/DBModels/Misc.cs b/ThuInfoWeb/DBModels/Misc.cs index 99c7dc7..9a351b8 100644 --- a/ThuInfoWeb/DBModels/Misc.cs +++ b/ThuInfoWeb/DBModels/Misc.cs @@ -1,28 +1,30 @@ using FreeSql.DataAnnotations; -namespace ThuInfoWeb.DBModels +namespace ThuInfoWeb.DBModels; + +/// +/// This should be only one record in database. +/// +public class Misc { + [Column(IsPrimary = true)] + public int Id { get; init; } = 1; + + /// + /// The url data of WeChat group qrcode. + /// + [Column(StringLength = -1, IsNullable = false)] + public string QrCodeContent { get; init; } = string.Empty; + + /// + /// The url of Apk. + /// + [Column(StringLength = -1, IsNullable = false)] + public string ApkUrl { get; init; } = string.Empty; + /// - /// This should be only one record in database. + /// The interface version of new school card. /// - public class Misc - { - [Column(IsPrimary = true)] - public int Id { get; set; } = 1; - /// - /// The url data of wechat group qrcode. - /// - [Column(StringLength = -1)] - public string QrCodeContent { get; set; } - /// - /// The url of Apk. - /// - [Column(StringLength = -1)] - public string ApkUrl { get; set; } - /// - /// The interface version of new school card. - /// - [Column] - public int CardIVersion { get; set; } - } + [Column(IsNullable = false)] + public int CardIVersion { get; init; } } diff --git a/ThuInfoWeb/DBModels/Request.cs b/ThuInfoWeb/DBModels/Request.cs index ccee302..39b4bbd 100644 --- a/ThuInfoWeb/DBModels/Request.cs +++ b/ThuInfoWeb/DBModels/Request.cs @@ -1,14 +1,21 @@ using FreeSql.DataAnnotations; -namespace ThuInfoWeb.DBModels +namespace ThuInfoWeb.DBModels; + +public class Request { - public class Request - { - [Column(IsIdentity = true, IsPrimary = true)] - public int Id { get; set; } - public string Method { get; set; } - public string Path { get; set; } - public DateTime Time { get; set; } - public uint Ip { get; set; } - } + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + + [Column(IsNullable = false)] + public string Method { get; set; } = string.Empty; + + [Column(IsNullable = false)] + public string Path { get; set; } = string.Empty; + + [Column(IsNullable = false)] + public DateTime Time { get; set; } + + [Column(IsNullable = false)] + public uint Ip { get; set; } } diff --git a/ThuInfoWeb/DBModels/Socket.cs b/ThuInfoWeb/DBModels/Socket.cs index 96595f9..7603b66 100644 --- a/ThuInfoWeb/DBModels/Socket.cs +++ b/ThuInfoWeb/DBModels/Socket.cs @@ -1,20 +1,28 @@ using FreeSql.DataAnnotations; -namespace ThuInfoWeb.DBModels +namespace ThuInfoWeb.DBModels; + +public class Socket { - public class Socket + public enum SocketStatus { - [Column(IsPrimary = true)] - public int SeatId { get; set; } - public int SectionId { get; set; } - public SocketStatus Status { get; set; } - public DateTime CreatedTime { get; set; } - public DateTime UpdatedTime { get; set; } - public enum SocketStatus - { - Unknown, - Available, - Unavailable - } + Unknown, + Available, + Unavailable } + + [Column(IsPrimary = true)] + public int SeatId { get; set; } + + [Column(IsNullable = false)] + public int SectionId { get; set; } + + [Column(IsNullable = false)] + public SocketStatus Status { get; set; } + + [Column(IsNullable = false)] + public DateTime CreatedTime { get; set; } + + [Column(IsNullable = false)] + public DateTime UpdatedTime { get; set; } } diff --git a/ThuInfoWeb/DBModels/Startup.cs b/ThuInfoWeb/DBModels/Startup.cs index 154a5bc..529fe7c 100644 --- a/ThuInfoWeb/DBModels/Startup.cs +++ b/ThuInfoWeb/DBModels/Startup.cs @@ -4,8 +4,9 @@ namespace ThuInfoWeb.DBModels; public class Startup { - [Column(IsPrimary = true,IsIdentity = true)] - public int Id { get; set; } + [Column(IsPrimary = true, IsIdentity = true)] + public int Id { get; init; } - public DateTime CreatedTime { get; set; } -} \ No newline at end of file + [Column(IsNullable = false)] + public DateTime CreatedTime { get; init; } +} diff --git a/ThuInfoWeb/DBModels/Usage.cs b/ThuInfoWeb/DBModels/Usage.cs index 5245389..aef16da 100644 --- a/ThuInfoWeb/DBModels/Usage.cs +++ b/ThuInfoWeb/DBModels/Usage.cs @@ -1,33 +1,37 @@ using FreeSql.DataAnnotations; -namespace ThuInfoWeb.DBModels +namespace ThuInfoWeb.DBModels; + +public class Usage { - public class Usage + public enum FunctionType { - [Column(IsPrimary = true,IsIdentity = true)] - public int Id { get; set; } - public FunctionType Function { get; set; } - public DateTime CreatedTime { get; set; } - public enum FunctionType - { - PhysicalExam, - TeachingEvaluation, - Report, - Classrooms, - Library, - GymnasiumReg, - PrivateRooms, - Expenditures, - Bank, - Invoice, - WasherInfo, - QZYQ, - DormScore, - Electricity, - NetworkDetail, - OnlineDevices, - SchoolCalendar, - CampusCard - } + PhysicalExam, + TeachingEvaluation, + Report, + Classrooms, + Library, + GymnasiumReg, + PrivateRooms, + Expenditures, + Bank, + Invoice, + WasherInfo, + QZYQ, + DormScore, + Electricity, + NetworkDetail, + OnlineDevices, + SchoolCalendar, + CampusCard } + + [Column(IsPrimary = true, IsIdentity = true)] + public int Id { get; set; } + + [Column(IsNullable = false)] + public FunctionType Function { get; set; } + + [Column(IsNullable = false)] + public DateTime CreatedTime { get; set; } } diff --git a/ThuInfoWeb/DBModels/User.cs b/ThuInfoWeb/DBModels/User.cs index dddf5ea..149a393 100644 --- a/ThuInfoWeb/DBModels/User.cs +++ b/ThuInfoWeb/DBModels/User.cs @@ -1,14 +1,21 @@ using FreeSql.DataAnnotations; -namespace ThuInfoWeb.DBModels +namespace ThuInfoWeb.DBModels; + +public class User { - public class User - { - [Column(IsIdentity = true, IsPrimary = true)] - public int Id { get; set; } - public string Name { get; set; } - public string PasswordHash { get; set; } - public bool IsAdmin { get; set; } - public DateTime CreatedTime { get; set; } - } + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; set; } + + [Column(IsNullable = false)] + public string Name { get; set; } = string.Empty; + + [Column(IsNullable = false)] + public string PasswordHash { get; set; } = string.Empty; + + [Column(IsNullable = false)] + public bool IsAdmin { get; set; } + + [Column(IsNullable = false)] + public DateTime CreatedTime { get; set; } } diff --git a/ThuInfoWeb/DBModels/Version.cs b/ThuInfoWeb/DBModels/Version.cs index 4a98190..0e86342 100644 --- a/ThuInfoWeb/DBModels/Version.cs +++ b/ThuInfoWeb/DBModels/Version.cs @@ -1,15 +1,21 @@ using FreeSql.DataAnnotations; -namespace ThuInfoWeb.DBModels +namespace ThuInfoWeb.DBModels; + +public class Version { - public class Version - { - [Column(IsIdentity = true, IsPrimary = true)] - public int Id { get; set; } - public string VersionName { get; set; } - [Column(StringLength = -1)] - public string ReleaseNote { get; set; } - public DateTime CreatedTime { get; set; } - public bool IsAndroid { get; set; } - } + [Column(IsIdentity = true, IsPrimary = true)] + public int Id { get; init; } + + [Column(IsNullable = false)] + public string VersionName { get; init; } = string.Empty; + + [Column(StringLength = -1, IsNullable = false)] + public string ReleaseNote { get; init; } = string.Empty; + + [Column(IsNullable = false)] + public DateTime CreatedTime { get; init; } + + [Column(IsNullable = false)] + public bool IsAndroid { get; init; } } diff --git a/ThuInfoWeb/Data.cs b/ThuInfoWeb/Data.cs index 3382f56..02b897c 100644 --- a/ThuInfoWeb/Data.cs +++ b/ThuInfoWeb/Data.cs @@ -1,167 +1,211 @@ -using ThuInfoWeb.DBModels; +using FreeSql; +using FreeSql.Internal; +using ThuInfoWeb.DBModels; using Version = ThuInfoWeb.DBModels.Version; -namespace ThuInfoWeb +namespace ThuInfoWeb; + +public class Data { - public class Data - { - private readonly IFreeSql _fsql; - - /// - /// - /// - /// the connection string - /// if env is development, use local sqlite database instead of remote postgresql. The DB file will be created automatically. - public Data(string connectionString, bool isDevelopment) - { - if (isDevelopment) - _fsql = new FreeSql.FreeSqlBuilder() - .UseConnectionString(FreeSql.DataType.Sqlite, "Data Source=test.db") - .UseAutoSyncStructure(true) - .UseNameConvert(FreeSql.Internal.NameConvertType.ToLower) - .UseMonitorCommand(x => Console.WriteLine(x.CommandText)) - .Build(); - else - _fsql = new FreeSql.FreeSqlBuilder() - .UseConnectionString(FreeSql.DataType.PostgreSQL, connectionString) - .UseAutoSyncStructure(true) - .UseNameConvert(FreeSql.Internal.NameConvertType.ToLower) - .Build(); - // Check if there is a misc record in database, create one if not exist. - if (!_fsql.Select().Any()) - _fsql.Insert(new Misc()).ExecuteAffrows(); - } - - public async Task GetMiscAsync() - => await _fsql.Select().FirstAsync(); - - public async Task UpdateMiscAsync(Misc misc) - => await _fsql.Update().SetSource(misc).ExecuteAffrowsAsync(); - - public async Task GetUserAsync(string name) - => await _fsql.Select().Where(x => x.Name == name).ToOneAsync(); - - public async Task CheckUserAsync(string name) - => await _fsql.Select().AnyAsync(x => x.Name == name); - - public async Task CreateUserAsync(User user) - => (await _fsql.Select().AnyAsync(x => x.Name == user.Name)) - ? 0 - : await _fsql.Insert(user).ExecuteAffrowsAsync(); - - public async Task ChangeUserPasswordAsync(string name, string passwordhash) - => await _fsql.Update().Where(x => x.Name == name).Set(x => x.PasswordHash, passwordhash) - .ExecuteAffrowsAsync(); - - public async Task GetAnnounceAsync(int? id = null) - => id is null - ? await _fsql.Select().OrderByDescending(x => x.Id).FirstAsync() - : await _fsql.Select().Where(x => x.Id == id).ToOneAsync(); - - public async Task GetActiveAnnounceAsync(int? id = null) - => id is null - ? await _fsql.Select().OrderByDescending(x => x.Id).Where(x => x.IsActive).FirstAsync() - : await _fsql.Select().Where(x => x.Id == id && x.IsActive).ToOneAsync(); - - public async Task> GetAnnouncesAsync(int page, int pageSize) - => await _fsql.Select().OrderByDescending(x => x.Id).Page(page, pageSize).ToListAsync(); - - public async Task> GetActiveAnnouncesAsync(int page, int pageSize) - => await _fsql.Select().OrderByDescending(x => x.Id).Page(page, pageSize).Where(x => x.IsActive) - .ToListAsync(); - - public async Task CreateAnnounceAsync(Announce a) - => await _fsql.Insert(a).ExecuteAffrowsAsync(); - - public async Task UpdateAnnounceStatusAsync(int id, bool toActive) - => await _fsql.Update().Where(x => x.Id == id).Set(x => x.IsActive, toActive) - .ExecuteAffrowsAsync(); - - public async Task DeleteAnnounceAsync(int id) - => await _fsql.Delete().Where(x => x.Id == id).ExecuteAffrowsAsync(); - - public async Task CreateFeedbackAsync(Feedback feedback) - => await _fsql.Insert(feedback).ExecuteAffrowsAsync(); - - public async Task GetFeedbackAsync(int? id = null) - => id is null - ? await _fsql.Select().OrderByDescending(x => x.Id).FirstAsync() - : await _fsql.Select().Where(x => x.Id == id).ToOneAsync(); - - public async Task> GetFeedbacksAsync(int page, int pageSize) - => await _fsql.Select().OrderByDescending(x => x.Id).Page(page, pageSize).ToListAsync(); - - public async Task> GetAllRepliedFeedbacksAsync() - => await _fsql.Select().Where(x => !string.IsNullOrEmpty(x.Reply)) - .OrderByDescending(x => x.RepliedTime).ToListAsync(); - - public async Task DeleteFeedbackAsync(int id) - => await _fsql.Delete().Where(x => x.Id == id).ExecuteAffrowsAsync(); - - public async Task ReplyFeedbackAsync(int id, string reply, string replyer) - => await _fsql.Update().Where(x => x.Id == id).Set(x => x.Reply, reply) - .Set(x => x.ReplierName, replyer).Set(x => x.RepliedTime, DateTime.Now).ExecuteAffrowsAsync(); - - public async Task> GetSocketsAsync(int sectionId) - => await _fsql.Select().Where(x => x.SectionId == sectionId).ToListAsync(); - - public async Task UpdateSocketAsync(int seatId, bool isAvailable) - => await _fsql.Update().Where(x => x.SeatId == seatId).Set(x => x.Status, - isAvailable ? Socket.SocketStatus.Available : Socket.SocketStatus.Unavailable).ExecuteAffrowsAsync(); - - public async Task CreateVersionAsync(Version version) - => await _fsql.Insert(version).ExecuteAffrowsAsync(); - - public async Task GetVersionAsync(bool isAndroid) - => await _fsql.Select().Where(x => x.IsAndroid == isAndroid).OrderByDescending(x => x.CreatedTime) - .FirstAsync(); - - public async Task CreateHttpRequestLogAsync(Request r) - => await _fsql.Insert(r).ExecuteAffrowsAsync(); + private readonly IFreeSql _fsql; + + /// + /// + /// the connection string + /// + /// if env is development, use local sqlite database instead of remote postgresql. The DB file + /// will be created automatically. + /// + public Data(string connectionString, bool isDevelopment) + { + if (isDevelopment) + _fsql = new FreeSqlBuilder() + .UseConnectionString(DataType.Sqlite, "Data Source=test.db") + .UseAutoSyncStructure(true) + .UseNameConvert(NameConvertType.ToLower) + .UseMonitorCommand(x => Console.WriteLine(x.CommandText)) + .Build(); + else + _fsql = new FreeSqlBuilder() + .UseConnectionString(DataType.PostgreSQL, connectionString) + .UseAutoSyncStructure(true) + .UseNameConvert(NameConvertType.ToLower) + .Build(); + // Check if there is a misc record in database, create one if not exist. + if (!_fsql.Select().Any()) + _fsql.Insert(new Misc()).ExecuteAffrows(); + } + + public async Task GetMiscAsync() + { + return await _fsql.Select().FirstAsync(); + } + + public async Task UpdateMiscAsync(Misc misc) + { + return await _fsql.Update().SetSource(misc).ExecuteAffrowsAsync(); + } + + public async Task GetUserAsync(string name) + { + return await _fsql.Select().Where(x => x.Name == name).ToOneAsync(); + } + + public async Task CheckUserAsync(string name) + { + return await _fsql.Select().AnyAsync(x => x.Name == name); + } + + public async Task CreateUserAsync(User user) + { + return await _fsql.Select().AnyAsync(x => x.Name == user.Name) + ? 0 + : await _fsql.Insert(user).ExecuteAffrowsAsync(); + } + + public async Task ChangeUserPasswordAsync(string name, string passwordHash) + { + return await _fsql.Update().Where(x => x.Name == name).Set(x => x.PasswordHash, passwordHash) + .ExecuteAffrowsAsync(); + } + + public async Task GetAnnounceAsync(int? id = null) + { + return id is null + ? await _fsql.Select().OrderByDescending(x => x.Id).FirstAsync() + : await _fsql.Select().Where(x => x.Id == id).ToOneAsync(); + } + + public async Task GetActiveAnnounceAsync(int? id = null) + { + return id is null + ? await _fsql.Select().OrderByDescending(x => x.Id).Where(x => x.IsActive).FirstAsync() + : await _fsql.Select().Where(x => x.Id == id && x.IsActive).ToOneAsync(); + } + + public async Task> GetAnnouncesAsync(int page, int pageSize) + { + return await _fsql.Select().OrderByDescending(x => x.Id).Page(page, pageSize).ToListAsync(); + } + + public async Task> GetActiveAnnouncesAsync(int page, int pageSize) + { + return await _fsql.Select().OrderByDescending(x => x.Id).Page(page, pageSize).Where(x => x.IsActive) + .ToListAsync(); + } + + public async Task CreateAnnounceAsync(Announce a) + { + return await _fsql.Insert(a).ExecuteAffrowsAsync(); + } + + public async Task UpdateAnnounceStatusAsync(int id, bool toActive) + { + return await _fsql.Update().Where(x => x.Id == id).Set(x => x.IsActive, toActive) + .ExecuteAffrowsAsync(); + } + + public async Task DeleteAnnounceAsync(int id) + { + return await _fsql.Delete().Where(x => x.Id == id).ExecuteAffrowsAsync(); + } + + public async Task CreateFeedbackAsync(Feedback feedback) + { + return await _fsql.Insert(feedback).ExecuteAffrowsAsync(); + } + + public async Task GetFeedbackAsync(int? id = null) + { + return id is null + ? await _fsql.Select().OrderByDescending(x => x.Id).FirstAsync() + : await _fsql.Select().Where(x => x.Id == id).ToOneAsync(); + } + + public async Task> GetFeedbacksAsync(int page, int pageSize) + { + return await _fsql.Select().OrderByDescending(x => x.Id).Page(page, pageSize).ToListAsync(); + } + + public async Task> GetAllRepliedFeedbacksAsync() + { + return await _fsql.Select().Where(x => !string.IsNullOrEmpty(x.Reply)) + .OrderByDescending(x => x.RepliedTime).ToListAsync(); + } + + public async Task DeleteFeedbackAsync(int id) + { + return await _fsql.Delete().Where(x => x.Id == id).ExecuteAffrowsAsync(); + } + + public async Task ReplyFeedbackAsync(int id, string reply, string replier) + { + return await _fsql.Update().Where(x => x.Id == id).Set(x => x.Reply, reply) + .Set(x => x.ReplierName, replier).Set(x => x.RepliedTime, DateTime.Now).ExecuteAffrowsAsync(); + } + + public async Task> GetSocketsAsync(int sectionId) + { + return await _fsql.Select().Where(x => x.SectionId == sectionId).ToListAsync(); + } + + public async Task UpdateSocketAsync(int seatId, bool isAvailable) + { + return await _fsql.Update().Where(x => x.SeatId == seatId).Set(x => x.Status, + isAvailable ? Socket.SocketStatus.Available : Socket.SocketStatus.Unavailable).ExecuteAffrowsAsync(); + } - public async Task CreateUsageAsync(Usage u) - => await _fsql.Insert(u).ExecuteAffrowsAsync(); + public async Task CreateVersionAsync(Version version) + { + return await _fsql.Insert(version).ExecuteAffrowsAsync(); + } + + public async Task GetVersionAsync(bool isAndroid) + { + return await _fsql.Select().Where(x => x.IsAndroid == isAndroid).OrderByDescending(x => x.CreatedTime) + .FirstAsync(); + } + + public async Task CreateHttpRequestLogAsync(Request r) + { + return await _fsql.Insert(r).ExecuteAffrowsAsync(); + } + + public async Task CreateUsageAsync(Usage u) + { + return await _fsql.Insert(u).ExecuteAffrowsAsync(); + } - public async Task> GetUsageAsync() - => await _fsql.Select().GroupBy(x => x.Function).ToDictionaryAsync(x => x.Count()); + public async Task> GetUsageAsync() + { + return await _fsql.Select().GroupBy(x => x.Function).ToDictionaryAsync(x => x.Count()); + } - public async Task CreateStartupAsync(Startup s) - => await _fsql.Insert(s).ExecuteAffrowsAsync(); + public async Task CreateStartupAsync(Startup s) + { + return await _fsql.Insert(s).ExecuteAffrowsAsync(); + } - public async Task> GetStartupDataAsync() - => await _fsql.Select().GroupBy(x => x.CreatedTime.ToString("yyyy MM")) - .OrderBy(x=>x.Key) - .ToDictionaryAsync(x => x.Count()); + public async Task> GetStartupDataAsync() + { + return await _fsql.Select().GroupBy(x => x.CreatedTime.ToString("yyyy MM")) + .OrderBy(x => x.Key) + .ToDictionaryAsync(x => x.Count()); + } #if DEBUG - public async Task GenStartupDataAsync() - { - var s1 = new Startup() - { - CreatedTime = DateTime.Now - TimeSpan.FromDays(30) - }; - for (int i = 0; i < 10; i++) - { - await _fsql.Insert(s1).ExecuteAffrowsAsync(); - } - - var s2 = new Startup() - { - CreatedTime = DateTime.Now - TimeSpan.FromDays(60) - }; - for (int i = 0; i < 20; i++) - { - await _fsql.Insert(s2).ExecuteAffrowsAsync(); - } - - var s3 = new Startup() - { - CreatedTime = DateTime.Now - TimeSpan.FromDays(90) - }; - for (int i = 0; i < 30; i++) - { - await _fsql.Insert(s3).ExecuteAffrowsAsync(); - } - } -#endif + public async Task GenStartupDataAsync() + { + var s1 = new Startup { CreatedTime = DateTime.Now - TimeSpan.FromDays(30) }; + for (var i = 0; i < 10; i++) + await _fsql.Insert(s1).ExecuteAffrowsAsync(); + + var s2 = new Startup { CreatedTime = DateTime.Now - TimeSpan.FromDays(60) }; + for (var i = 0; i < 20; i++) + await _fsql.Insert(s2).ExecuteAffrowsAsync(); + + var s3 = new Startup { CreatedTime = DateTime.Now - TimeSpan.FromDays(90) }; + for (var i = 0; i < 30; i++) + await _fsql.Insert(s3).ExecuteAffrowsAsync(); } -} \ No newline at end of file +#endif +} diff --git a/ThuInfoWeb/Dtos/FeedbackDto.cs b/ThuInfoWeb/Dtos/FeedbackDto.cs index e2e1993..51c0fba 100644 --- a/ThuInfoWeb/Dtos/FeedbackDto.cs +++ b/ThuInfoWeb/Dtos/FeedbackDto.cs @@ -1,19 +1,21 @@ using System.ComponentModel.DataAnnotations; -namespace ThuInfoWeb.Dtos +namespace ThuInfoWeb.Dtos; + +public class FeedbackDto { - public class FeedbackDto - { - [Required] - public string Content { get; set; } - [MaxLength(256)] - public string Contact { get; set; } = string.Empty; - [Required] - public string OS { get; set; } - [Required] - public string AppVersion { get; set; } - [Required] - public string PhoneModel { get; set; } - - } + [Required] + public string Content { get; init; } = string.Empty; + + [MaxLength(256)] + public string Contact { get; init; } = string.Empty; + + [Required] + public string OS { get; init; } = string.Empty; + + [Required] + public string AppVersion { get; init; } = string.Empty; + + [Required] + public string PhoneModel { get; init; } = string.Empty; } diff --git a/ThuInfoWeb/Dtos/LostAndFoundDto.cs b/ThuInfoWeb/Dtos/LostAndFoundDto.cs index 31a2c21..8d48fbd 100644 --- a/ThuInfoWeb/Dtos/LostAndFoundDto.cs +++ b/ThuInfoWeb/Dtos/LostAndFoundDto.cs @@ -1,14 +1,16 @@ using System.ComponentModel.DataAnnotations; -namespace ThuInfoWeb.Dtos +namespace ThuInfoWeb.Dtos; + +public class LostAndFoundDto { - public class LostAndFoundDto - { - public int Id { get; set; } - [Required] - public string Message { get; set; } - [Required] - public int SenderId { get; set; } - public int? TargetId { get; set; } - } + public int Id { get; set; } + + [Required] + public string? Message { get; set; } + + [Required] + public int SenderId { get; set; } + + public int? TargetId { get; set; } } diff --git a/ThuInfoWeb/Dtos/SocketDto.cs b/ThuInfoWeb/Dtos/SocketDto.cs index 0d6880c..3b8fcae 100644 --- a/ThuInfoWeb/Dtos/SocketDto.cs +++ b/ThuInfoWeb/Dtos/SocketDto.cs @@ -1,18 +1,19 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; -namespace ThuInfoWeb.Dtos +namespace ThuInfoWeb.Dtos; + +public class SocketDto { - public class SocketDto - { - [Required] - public int? SeatId { get; set; } - [Required, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public bool? IsAvailable { get; set; } - public int SectionId { get; set; } - public DateTime CreatedTime { get; set; } - public DateTime UpdatedTime { get; set; } - public string? Status { get; set; } - - } + [Required] + public int? SeatId { get; init; } + + [Required] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public bool? IsAvailable { get; set; } + + public int SectionId { get; set; } + public DateTime CreatedTime { get; set; } + public DateTime UpdatedTime { get; set; } + public string? Status { get; set; } } diff --git a/ThuInfoWeb/Dtos/VersionDto.cs b/ThuInfoWeb/Dtos/VersionDto.cs index ca9aa0b..37f56c4 100644 --- a/ThuInfoWeb/Dtos/VersionDto.cs +++ b/ThuInfoWeb/Dtos/VersionDto.cs @@ -1,10 +1,12 @@ -namespace ThuInfoWeb.Dtos +namespace ThuInfoWeb.Dtos; + +public class VersionDto { - public class VersionDto - { - public string VersionName { get; set; } - public string ReleaseNote { get; set; } - public DateTime CreatedTime { get; set; } - public string DownloadUrl { get; set; } - } + public string VersionName { get; set; } = string.Empty; + + public string ReleaseNote { get; set; } = string.Empty; + + public DateTime CreatedTime { get; set; } + + public string DownloadUrl { get; set; } = string.Empty; } diff --git a/ThuInfoWeb/Extension.cs b/ThuInfoWeb/Extension.cs index 179f133..7847c02 100644 --- a/ThuInfoWeb/Extension.cs +++ b/ThuInfoWeb/Extension.cs @@ -1,19 +1,22 @@ using System.Security.Cryptography; using System.Text; +using System.Text.RegularExpressions; -namespace ThuInfoWeb +namespace ThuInfoWeb; + +public static partial class Extension { - public static class Extension + [GeneratedRegex(@"^\d+\.\d+\.\d+$")] + private static partial Regex VersionRegex(); + + public static string ToSHA256Hex(this string s) + { + var data = SHA256.HashData(Encoding.ASCII.GetBytes(s)); + return data.Aggregate("", (current, b) => current + b.ToString("x").PadLeft(2, '0')); + } + + public static bool IsValidVersionNumber(this string s) { - public static string ToSHA256Hex(this string s) - { - var data = SHA256.HashData(Encoding.ASCII.GetBytes(s)); - string output = ""; - foreach (var b in data) - { - output += b.ToString("x").PadLeft(2, '0'); - } - return output; - } + return VersionRegex().IsMatch(s); } } diff --git a/ThuInfoWeb/HttpLoggingMiddleware.cs b/ThuInfoWeb/HttpLoggingMiddleware.cs index 8c7bbf2..1d27e66 100644 --- a/ThuInfoWeb/HttpLoggingMiddleware.cs +++ b/ThuInfoWeb/HttpLoggingMiddleware.cs @@ -1,48 +1,44 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using System.Net; -using System.Threading.Tasks; +using System.Net; using ThuInfoWeb.DBModels; -namespace ThuInfoWeb +namespace ThuInfoWeb; + +// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project +public class HttpLoggingMiddleware { - // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project - public class HttpLoggingMiddleware - { - private readonly RequestDelegate _next; + private readonly RequestDelegate _next; - public HttpLoggingMiddleware(RequestDelegate next) - { - _next = next; - } + public HttpLoggingMiddleware(RequestDelegate next) + { + _next = next; + } - public async Task Invoke(HttpContext context, Data data) + public async Task Invoke(HttpContext context, Data data) + { + var path = context.Request.Path; + if (!path.StartsWithSegments("/api")) { - var path = context.Request.Path; - if (!path.StartsWithSegments("/api")) + var ip = context.Connection.RemoteIpAddress ?? IPAddress.Parse("0.0.0.0"); + var ipBytes = ip.GetAddressBytes().Reverse().ToArray(); + var r = new Request { - var ip = context.Connection.RemoteIpAddress; - if (ip is null) ip = IPAddress.Parse("0.0.0.0"); - var ipBytes = ip.GetAddressBytes().Reverse().ToArray(); - var r = new Request() - { - Method = context.Request.Method, - Path = path, - Ip = BitConverter.ToUInt32(ipBytes), - Time = DateTime.Now - }; - await data.CreateHttpRequestLogAsync(r); - } - await _next(context); + Method = context.Request.Method, + Path = path, + Ip = BitConverter.ToUInt32(ipBytes), + Time = DateTime.Now + }; + await data.CreateHttpRequestLogAsync(r); } + + await _next(context); } +} - // Extension method used to add the middleware to the HTTP request pipeline. - public static class HttpLoggingMiddlewareExtensions +// Extension method used to add the middleware to the HTTP request pipeline. +public static class HttpLoggingMiddlewareExtensions +{ + public static IApplicationBuilder UseHttpLoggingMiddleware(this IApplicationBuilder builder) { - public static IApplicationBuilder UseHttpLoggingMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } + return builder.UseMiddleware(); } } diff --git a/ThuInfoWeb/Hubs/ScheduleSyncHub.cs b/ThuInfoWeb/Hubs/ScheduleSyncHub.cs index 05a7e4c..db86e5f 100644 --- a/ThuInfoWeb/Hubs/ScheduleSyncHub.cs +++ b/ThuInfoWeb/Hubs/ScheduleSyncHub.cs @@ -5,26 +5,11 @@ namespace ThuInfoWeb.Hubs; public class ScheduleSyncHub : Hub { - private static readonly List SyncClients = new(); - private static readonly List ConfirmSyncClients = new(); + private static readonly List SyncClients = []; + private static readonly List ConfirmSyncClients = []; public void StartMatch(string user, bool isSending) { - static string GenToken() - { - var rand = Random.Shared; - var b = new byte[16]; - rand.NextBytes(b); - var ret = ""; - var hash = MD5.HashData(b); - for (int i = 0; i < 3; i++) - { - ret += hash[i].ToString("x").PadLeft(2,'0'); - } - - return ret; - } - if (SyncClients.Exists(x => x.User == user)) // Existing a user, try to match it. { var token = GenToken(); @@ -32,37 +17,52 @@ static string GenToken() { if (!SyncClients.Exists(x => x.User == user && x.IsSending == false)) return; // no receiver, just return - var target = SyncClients.Where(x => x.User == user).First(); + var target = SyncClients.First(x => x.User == user); SyncClients.Remove(target); _ = Clients.Clients(Context.ConnectionId, target.Id).SendAsync("ConfirmMatch", token); } else // is receiving { - if (!SyncClients.Exists(x => x.User == user && x.IsSending == true)) + if (!SyncClients.Exists(x => x.User == user && x.IsSending)) return; // no sender, just return - var targetId = Context.ConnectionId; - var sender = SyncClients.Where(x => x.User == user).First(); + var sender = SyncClients.First(x => x.User == user); SyncClients.Remove(sender); _ = Clients.Clients(sender.Id, Context.ConnectionId).SendAsync("ConfirmMatch", token); } } else // No user can be matched, waiting { - SyncClients.Add(new(Context.ConnectionId, user, isSending)); + SyncClients.Add(new SyncClient(Context.ConnectionId, user, isSending)); + } + + return; + + static string GenToken() + { + var rand = Random.Shared; + var b = new byte[16]; + rand.NextBytes(b); + var ret = ""; + var hash = MD5.HashData(b); + for (var i = 0; i < 3; i++) + ret += hash[i].ToString("x").PadLeft(2, '0'); + + return ret; } } public void ConfirmMatch(string user, string token, bool isSending) { - ConfirmSyncClients.Add(new(Context.ConnectionId, user, isSending, token)); + ConfirmSyncClients.Add(new ConfirmSyncClient(Context.ConnectionId, user, isSending, token)); var matchedClients = ConfirmSyncClients.FindAll(x => x.Token == token); if (matchedClients.Count != 2) return; if (!matchedClients.TrueForAll(x => x.Token == token)) // code mismatched return; - _ = Clients.Client(matchedClients.Find(x => x.IsSending).Id) - .SendAsync("SetTarget", matchedClients.Find(x => !x.IsSending).Id); - if(ConfirmSyncClients.Count>100) ConfirmSyncClients.Clear(); + _ = Clients.Client(matchedClients.Find(x => x.IsSending)!.Id) + .SendAsync("SetTarget", matchedClients.Find(x => !x.IsSending)!.Id); + if (ConfirmSyncClients.Count > 100) + ConfirmSyncClients.Clear(); } public void SendToTarget(string targetId, string schedulesJson) @@ -79,27 +79,16 @@ public override Task OnDisconnectedAsync(Exception? exception) return base.OnDisconnectedAsync(exception); } - private class SyncClient + private class SyncClient(string id, string user, bool isSending) { - public string Id { get; } - public string User { get; } - public bool IsSending { get; } - - public SyncClient(string id, string user, bool isSending) - { - Id = id; - User = user; - IsSending = isSending; - } + public string Id { get; } = id; + public string User { get; } = user; + public bool IsSending { get; } = isSending; } - private class ConfirmSyncClient : SyncClient + private class ConfirmSyncClient(string id, string user, bool isSending, string token) + : SyncClient(id, user, isSending) { - public string Token { get; } - - public ConfirmSyncClient(string id, string user, bool isSending, string token) : base(id, user, isSending) - { - Token = token; - } + public string Token { get; } = token; } -} \ No newline at end of file +} diff --git a/ThuInfoWeb/Models/AnnounceViewModel.cs b/ThuInfoWeb/Models/AnnounceViewModel.cs index 385c996..ad62271 100644 --- a/ThuInfoWeb/Models/AnnounceViewModel.cs +++ b/ThuInfoWeb/Models/AnnounceViewModel.cs @@ -1,27 +1,28 @@ using System.ComponentModel.DataAnnotations; -namespace ThuInfoWeb.Models +namespace ThuInfoWeb.Models; + +public class AnnounceViewModel { - public class AnnounceViewModel - { - [Required, Display(Name = "标题")] - public string? Title { get; set; } + [Required] + [Display(Name = "标题")] + public string? Title { get; init; } - [Required, Display(Name = "内容")] - public string? Content { get; set; } + [Required] + [Display(Name = "内容")] + public string? Content { get; init; } - public int Id { get; set; } + public int Id { get; init; } - public string? Author { get; set; } + public string? Author { get; init; } - public DateTime CreatedTime { get; set; } + public DateTime CreatedTime { get; init; } - public bool IsActive { get; set; } = true; + public bool IsActive { get; init; } = true; - [Display(Name = "对此版本及之前版本可见")] - public string? VisibleNotAfter { get; set; } + [Display(Name = "对此版本及之前版本可见")] + public string? VisibleNotAfter { get; init; } - [Display(Name = "对这些版本可见 (使用逗号分隔)")] - public string? VisibleExact { get; set; } - } + [Display(Name = "对这些版本可见 (使用逗号分隔)")] + public string? VisibleExact { get; init; } } diff --git a/ThuInfoWeb/Models/ChangePasswordViewModel.cs b/ThuInfoWeb/Models/ChangePasswordViewModel.cs index ff0dd2c..bd73ce7 100644 --- a/ThuInfoWeb/Models/ChangePasswordViewModel.cs +++ b/ThuInfoWeb/Models/ChangePasswordViewModel.cs @@ -1,14 +1,21 @@ using System.ComponentModel.DataAnnotations; -namespace ThuInfoWeb.Models +namespace ThuInfoWeb.Models; + +public class ChangePasswordViewModel { - public class ChangePasswordViewModel - { - [Required, Display(Name = "用户名")] - public string Name { get; set; } - [Required, DataType(DataType.Password), Display(Name = "旧密码")] - public string OldPassword { get; set; } - [Required, DataType(DataType.Password), Display(Name = "新密码"), RegularExpression("^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$", ErrorMessage = "密码必须并只能包含字母和数字,长度为6到20位")] - public string NewPassword { get; set; } - } + [Required] + [Display(Name = "用户名")] + public string? Name { get; init; } + + [Required] + [DataType(DataType.Password)] + [Display(Name = "旧密码")] + public string? OldPassword { get; init; } + + [Required] + [DataType(DataType.Password)] + [Display(Name = "新密码")] + [RegularExpression("^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$", ErrorMessage = "密码必须并只能包含字母和数字,长度为6到20位")] + public string? NewPassword { get; init; } } diff --git a/ThuInfoWeb/Models/ErrorViewModel.cs b/ThuInfoWeb/Models/ErrorViewModel.cs index 4cd00b4..74b8b16 100644 --- a/ThuInfoWeb/Models/ErrorViewModel.cs +++ b/ThuInfoWeb/Models/ErrorViewModel.cs @@ -1,9 +1,8 @@ -namespace ThuInfoWeb.Models +namespace ThuInfoWeb.Models; + +public class ErrorViewModel { - public class ErrorViewModel - { - public string? RequestId { get; set; } + public string? RequestId { get; init; } - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - } -} \ No newline at end of file + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); +} diff --git a/ThuInfoWeb/Models/FeedbackViewModel.cs b/ThuInfoWeb/Models/FeedbackViewModel.cs index 1cbc1c4..ade4245 100644 --- a/ThuInfoWeb/Models/FeedbackViewModel.cs +++ b/ThuInfoWeb/Models/FeedbackViewModel.cs @@ -1,13 +1,16 @@ using ThuInfoWeb.Dtos; -namespace ThuInfoWeb.Models +namespace ThuInfoWeb.Models; + +public class FeedbackViewModel : FeedbackDto { - public class FeedbackViewModel : FeedbackDto - { - public int Id { get; set; } - public DateTime CreatedTime { get; set; } - public string Reply { get; set; } = string.Empty; - public string ReplyerName { get; set; } = string.Empty; - public DateTime? RepliedTime { get; set; } - } + public int Id { get; init; } + + public DateTime CreatedTime { get; init; } + + public string? Reply { get; init; } + + public string? ReplierName { get; init; } + + public DateTime? RepliedTime { get; init; } } diff --git a/ThuInfoWeb/Models/LoginViewModel.cs b/ThuInfoWeb/Models/LoginViewModel.cs index cd9b1e0..247042c 100644 --- a/ThuInfoWeb/Models/LoginViewModel.cs +++ b/ThuInfoWeb/Models/LoginViewModel.cs @@ -1,12 +1,18 @@ using System.ComponentModel.DataAnnotations; -namespace ThuInfoWeb.Models +namespace ThuInfoWeb.Models; + +public class LoginViewModel { - public class LoginViewModel - { - [DataType(DataType.Text), Display(Name = "用户名"), Required, MaxLength(100)] - public string Name { get; set; } - [DataType(DataType.Password), Display(Name = "密码"), Required, MaxLength(20)] - public string Password { get; set; } - } + [DataType(DataType.Text)] + [Display(Name = "用户名")] + [Required] + [MaxLength(100)] + public string? Name { get; init; } + + [DataType(DataType.Password)] + [Display(Name = "密码")] + [Required] + [MaxLength(20)] + public string? Password { get; init; } } diff --git a/ThuInfoWeb/Models/MiscViewModel.cs b/ThuInfoWeb/Models/MiscViewModel.cs index 456cf9f..e6f59b2 100644 --- a/ThuInfoWeb/Models/MiscViewModel.cs +++ b/ThuInfoWeb/Models/MiscViewModel.cs @@ -1,14 +1,17 @@ using System.ComponentModel.DataAnnotations; -namespace ThuInfoWeb.Models +namespace ThuInfoWeb.Models; + +public class MiscViewModel { - public class MiscViewModel - { - [Required, Url] - public string QrCodeContent { get; set; } - [Required, Url] - public string ApkUrl { get; set; } - [Required] - public int CardIVersion { get; set; } - } + [Required] + [Url] + public string? QrCodeContent { get; init; } + + [Required] + [Url] + public string? ApkUrl { get; init; } + + [Required] + public int CardIVersion { get; init; } } diff --git a/ThuInfoWeb/Models/RegisterViewModel.cs b/ThuInfoWeb/Models/RegisterViewModel.cs index e887c7b..cf43684 100644 --- a/ThuInfoWeb/Models/RegisterViewModel.cs +++ b/ThuInfoWeb/Models/RegisterViewModel.cs @@ -1,12 +1,16 @@ using System.ComponentModel.DataAnnotations; -namespace ThuInfoWeb.Models +namespace ThuInfoWeb.Models; + +public class RegisterViewModel { - public class RegisterViewModel - { - [Required, Display(Name = "用户名")] - public string Name { get; set; } - [Required, DataType(DataType.Password), Display(Name = "密码"), RegularExpression("^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$", ErrorMessage = "密码必须并只能包含字母和数字,长度为6到20位")] - public string Password { get; set; } - } + [Required] + [Display(Name = "用户名")] + public string? Name { get; init; } + + [Required] + [DataType(DataType.Password)] + [Display(Name = "密码")] + [RegularExpression("^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$", ErrorMessage = "密码必须并只能包含字母和数字,长度为6到20位")] + public string? Password { get; init; } } diff --git a/ThuInfoWeb/Models/UserViewModel.cs b/ThuInfoWeb/Models/UserViewModel.cs index 3355c4c..baa77d6 100644 --- a/ThuInfoWeb/Models/UserViewModel.cs +++ b/ThuInfoWeb/Models/UserViewModel.cs @@ -1,8 +1,12 @@ -namespace ThuInfoWeb.Models +using System.ComponentModel.DataAnnotations; + +namespace ThuInfoWeb.Models; + +public class UserViewModel { - public class UserViewModel - { - public string Name { get; set; } - public bool IsAdmin { get; set; } - } + [Required] + public string? Name { get; set; } + + [Required] + public bool IsAdmin { get; set; } = false; } diff --git a/ThuInfoWeb/Program.cs b/ThuInfoWeb/Program.cs index f696adf..9ccbba9 100644 --- a/ThuInfoWeb/Program.cs +++ b/ThuInfoWeb/Program.cs @@ -1,3 +1,4 @@ +using NLog; using NLog.Web; using ThuInfoWeb; using ThuInfoWeb.Bots; @@ -17,7 +18,8 @@ options.LoginPath = new PathString("/Home/Login"); options.AccessDeniedPath = new PathString("/deny"); }); -builder.Services.AddSingleton(new Data(builder.Configuration.GetConnectionString("Test"), builder.Environment.IsDevelopment())); +builder.Services.AddSingleton(new Data(builder.Configuration.GetConnectionString("Test") ?? "", + builder.Environment.IsDevelopment())); // builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); @@ -42,9 +44,8 @@ app.UseAuthentication(); app.UseAuthorization(); -app.MapControllerRoute( - name: "default", - pattern: "{controller}/{action}/{id?}"); +app.MapControllerRoute("default", + "{controller}/{action}/{id?}"); app.MapFallbackToFile("/", "index.html"); app.MapFallbackToFile("/index", "index.html"); @@ -52,8 +53,12 @@ app.MapFallbackToFile("/help", "help.html"); app.MapFallbackToFile("/privacy", "privacy.html"); app.MapFallbackToFile("/privacy-en", "privacy-en.html"); -app.MapFallback("/deny", async r => await r.Response.WriteAsync("access denied")); +app.MapFallback("/deny", async r => +{ + r.Response.StatusCode = 403; + await r.Response.WriteAsync("access denied"); +}); app.MapHub("/schedulesynchub"); app.Run(); -NLog.LogManager.Shutdown(); \ No newline at end of file +LogManager.Shutdown(); diff --git a/ThuInfoWeb/SecretManager.cs b/ThuInfoWeb/SecretManager.cs index 88f47f2..d71bc87 100644 --- a/ThuInfoWeb/SecretManager.cs +++ b/ThuInfoWeb/SecretManager.cs @@ -1,9 +1,3 @@ -namespace ThuInfoWeb -{ - public class SecretManager - { - public SecretManager() - { - } - } -} +namespace ThuInfoWeb; + +public class SecretManager; diff --git a/ThuInfoWeb/ThuInfoWeb.csproj b/ThuInfoWeb/ThuInfoWeb.csproj index 8585971..c7c3346 100644 --- a/ThuInfoWeb/ThuInfoWeb.csproj +++ b/ThuInfoWeb/ThuInfoWeb.csproj @@ -1,20 +1,20 @@  - - net8.0 - enable - enable - + + net8.0 + enable + enable + - - - + + + - - - - - - + + + + + + diff --git a/ThuInfoWeb/UserManager.cs b/ThuInfoWeb/UserManager.cs index 80d4bfc..d92576e 100644 --- a/ThuInfoWeb/UserManager.cs +++ b/ThuInfoWeb/UserManager.cs @@ -1,32 +1,20 @@ -using Microsoft.AspNetCore.Authentication; -using System.Security.Claims; +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; -namespace ThuInfoWeb +namespace ThuInfoWeb; + +public class UserManager(IHttpContextAccessor accessor) { - public class UserManager + public async Task DoLoginAsync(string name, bool isAdmin) { - private readonly IHttpContextAccessor _accessor; + var claims = new List { new(ClaimTypes.Name, name), new(ClaimTypes.Role, isAdmin ? "admin" : "guest") }; + var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "any")); + await accessor.HttpContext!.SignInAsync("Cookies", user, + new AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddMinutes(20) }); + } - public UserManager(IHttpContextAccessor accessor) - { - this._accessor = accessor; - } - public async Task DoLoginAsync(string name, bool isAdmin) - { - var claims = new List() - { - new Claim(ClaimTypes.Name,name), - new Claim(ClaimTypes.Role,isAdmin?"admin":"guest") - }; - var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "any")); - await _accessor.HttpContext.SignInAsync("Cookies", user, new AuthenticationProperties - { - ExpiresUtc = DateTime.UtcNow.AddMinutes(20) - }); - } - public async Task DoLogoutAsync() - { - await _accessor.HttpContext.SignOutAsync(); - } + public async Task DoLogoutAsync() + { + await accessor.HttpContext!.SignOutAsync(); } } diff --git a/ThuInfoWeb/VersionManager.cs b/ThuInfoWeb/VersionManager.cs index 4df4f05..1ceea89 100644 --- a/ThuInfoWeb/VersionManager.cs +++ b/ThuInfoWeb/VersionManager.cs @@ -3,59 +3,57 @@ using ThuInfoWeb.Dtos; using Version = ThuInfoWeb.DBModels.Version; -namespace ThuInfoWeb +namespace ThuInfoWeb; + +public class VersionManager(ILogger logger, Data data, IConfiguration configuration) { - public class VersionManager + public enum OS { - private readonly ILogger _logger; - private readonly Data _data; - private readonly HttpClient _client = new Func(() => - { - var client = new HttpClient(); - client.DefaultRequestHeaders.Add("user-agent", "aspnetcore/6.0"); - return client; - })(); - private Version _currentVersionOfAndroid; - private Version _currentVersionOfIOS; - private readonly object _lock = new(); - private bool isRunning; - public bool IsRunning + Android, + IOS + } + + private readonly HttpClient _client = new Func(() => + { + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("user-agent", "aspnetcore/6.0"); + return client; + })(); + + private readonly bool _internalNetworkMode = bool.Parse(configuration["InternalNetworkMode"] ?? "false"); + private readonly object _lock = new(); + private Version _currentVersionOfAndroid = data.GetVersionAsync(true).Result ?? new Version(); + private Version _currentVersionOfIOS = data.GetVersionAsync(false).Result ?? new Version(); + private bool _isRunning; + + public bool IsRunning + { + get { - get + lock (_lock) { - lock (_lock) - { - return isRunning; - } + return _isRunning; } - private set + } + private set + { + lock (_lock) { - lock (_lock) - { - isRunning = value; - } + _isRunning = value; } } + } - private readonly bool _internalNetworkMode; - - public VersionManager(ILogger logger, Data data,IConfiguration configuration) - { - this._logger = logger; - this._data = data; - this._internalNetworkMode = bool.Parse(configuration["InternalNetworkMode"]); - // initial current version from database in ctor. - this._currentVersionOfAndroid = data.GetVersionAsync(true).Result ?? new Version(); - this._currentVersionOfIOS = data.GetVersionAsync(false).Result ?? new Version(); - } - public VersionDto GetCurrentVersion(OS os) => os switch + public VersionDto GetCurrentVersion(OS os) + { + return os switch { OS.Android => new VersionDto { CreatedTime = _currentVersionOfAndroid.CreatedTime, DownloadUrl = "https://app.cs.tsinghua.edu.cn/api/apk", ReleaseNote = _currentVersionOfAndroid.ReleaseNote, - VersionName = _currentVersionOfAndroid.VersionName, + VersionName = _currentVersionOfAndroid.VersionName }, OS.IOS => new VersionDto { @@ -63,119 +61,122 @@ public VersionManager(ILogger logger, Data data,IConfiguration c DownloadUrl = "https://apps.apple.com/cn/app/thu-info/id1533968428", ReleaseNote = _currentVersionOfIOS.ReleaseNote, VersionName = _currentVersionOfIOS.VersionName - } + }, + _ => throw new ArgumentOutOfRangeException(nameof(os), os, null) }; - public async Task CheckUpdateAsync(OS os) + } + + public async Task CheckUpdateAsync(OS os) + { + IsRunning = true; + logger.LogInformation("Start checking update for {OS}, current version is {Version}", + os == OS.Android ? "Android" : "iOS", + os == OS.Android ? _currentVersionOfAndroid.VersionName : _currentVersionOfIOS.VersionName); + + try { - IsRunning = true; - _logger.LogInformation($"Start checking update for {(os == OS.Android ? "Android" : "iOS")}, current version is {(os == OS.Android ? _currentVersionOfAndroid.VersionName : _currentVersionOfIOS.VersionName)}"); - try + if (_internalNetworkMode) + { + if (os == OS.Android) + { + var content = await _client.GetStringAsync( + "https://stu.cs.tsinghua.edu.cn/thuinfo/version/android"); + var version = JsonSerializer.Deserialize(content)!; + if (version.VersionName == _currentVersionOfAndroid.VersionName) + logger.LogInformation("No newer version is available for Android (current version is {VersionName})," + + " check update for Android ok", version.VersionName); + + if (await data.CreateVersionAsync(version) != 1) + throw new Exception("Unknown Error"); + logger.LogInformation("Found new version for Android: {VersionName}, check update ok", + version.VersionName); + } + else + { + var content = await _client.GetStringAsync("https://stu.cs.tsinghua.edu.cn/thuinfo/version/ios"); + var version = JsonSerializer.Deserialize(content)!; + if (version.VersionName == _currentVersionOfIOS.VersionName) + logger.LogInformation("No newer version is available for iOS(current version is {VersionName}), check update for iOS ok", version.VersionName); + + if (await data.CreateVersionAsync(version) != 1) + throw new Exception("Unknown Error"); + logger.LogInformation("Found new version for iOS: {VersionName}, check update for iOS ok", version.VersionName); + } + } + else { - if (_internalNetworkMode) + if (os == OS.Android) { - if (os == OS.Android) + const string url = "https://api.github.com/repos/UNIDY2002/THUInfo/releases/latest"; + var content = await _client.GetStringAsync(url); + var json = JsonNode.Parse(content)!; + var versionName = (string)json["name"]!; + if (versionName == _currentVersionOfAndroid.VersionName) { - var content = await _client.GetStringAsync( - "https://stu.cs.tsinghua.edu.cn/thuinfo/version/android"); - var version = JsonSerializer.Deserialize(content)!; - if(version.VersionName == _currentVersionOfAndroid.VersionName) - _logger.LogInformation( - $"No newer version is available for Android(current version is {version.VersionName}), check update for Android ok."); - var result = await _data.CreateVersionAsync(version); - if (result != 1) throw new Exception("Unknown Error"); - else - _logger.LogInformation( - $"Found new version for Android: {version.VersionName}, check update ok"); + logger.LogInformation( + "No newer version is available for Android(current version is {VersionName}), check update for Android ok", + versionName); } else { - var content = await _client.GetStringAsync( - "https://stu.cs.tsinghua.edu.cn/thuinfo/version/ios"); - var version = JsonSerializer.Deserialize(content)!; - if(version.VersionName == _currentVersionOfIOS.VersionName) - _logger.LogInformation( - $"No newer version is available for iOS(current version is {version.VersionName}), check update for iOS ok."); - var result = await _data.CreateVersionAsync(version); - if (result != 1) throw new Exception("Unknown Error"); - else - _logger.LogInformation( - $"Found new version for iOS: {version.VersionName}, check update for iOS ok"); + var publishedAt = DateTime.Parse((string)json["published_at"]!).ToLocalTime(); + var releaseNote = (string)json["body"]!; + var version = new Version + { + CreatedTime = publishedAt, + IsAndroid = true, + ReleaseNote = releaseNote, + VersionName = versionName + }; + var result = await data.CreateVersionAsync(version); + if (result != 1) + throw new Exception("Unknown Error"); + logger.LogInformation("Found new version for Android: {VersionName}, check update ok", versionName); } } - else + else // handle ios { - if (os == OS.Android) + const string url = "https://itunes.apple.com/lookup?id=1533968428"; + var content = await _client.GetStringAsync(url); + var json = JsonNode.Parse(content)!["results"]!.AsArray()[0]!; + var versionName = (string)json["version"]!; + if (versionName == _currentVersionOfIOS.VersionName) { - var url = "https://api.github.com/repos/UNIDY2002/THUInfo/releases/latest"; - var content = await _client.GetStringAsync(url); - var json = JsonNode.Parse(content); - var versionName = (string)json["name"]; - if (versionName == _currentVersionOfAndroid.VersionName) - _logger.LogInformation( - $"No newer version is available for Android(current version is {versionName}), check update for Android ok."); - else - { - var publishedAt = DateTime.Parse((string)json["published_at"]).ToLocalTime(); - var releaseNote = (string)json["body"]; - var version = new Version() - { - CreatedTime = publishedAt, - IsAndroid = true, - ReleaseNote = releaseNote, - VersionName = versionName - }; - var result = await _data.CreateVersionAsync(version); - if (result != 1) throw new Exception("Unknown Error"); - else - _logger.LogInformation( - $"Found new version for Android: {versionName}, check update ok"); - } + logger.LogInformation( + "No newer version is available for iOS(current version is {VersionName}), check update for iOS ok", + versionName); } - else // handle ios + else { - var url = "https://itunes.apple.com/lookup?id=1533968428"; - var content = await _client.GetStringAsync(url); - var json = JsonNode.Parse(content)["results"].AsArray()[0]; - var versionName = (string)json["version"]; - if (versionName == _currentVersionOfIOS.VersionName) - _logger.LogInformation( - $"No newer version is available for iOS(current version is {versionName}), check update for iOS ok."); - else + var publishedAt = DateTime.Parse((string)json["currentVersionReleaseDate"]!).ToLocalTime(); + var releaseNote = (string)json["releaseNotes"]!; + var version = new Version { - var publishedAt = DateTime.Parse((string)json["currentVersionReleaseDate"]).ToLocalTime(); - var releaseNote = (string)json["releaseNotes"]; - var version = new Version() - { - CreatedTime = publishedAt, - IsAndroid = false, - ReleaseNote = releaseNote, - VersionName = versionName - }; - var result = await _data.CreateVersionAsync(version); - if (result != 1) throw new Exception("Unknown Error"); - else - _logger.LogInformation( - $"Found new version for iOS: {versionName}, check update for iOS ok"); - } + CreatedTime = publishedAt, + IsAndroid = false, + ReleaseNote = releaseNote, + VersionName = versionName + }; + var result = await data.CreateVersionAsync(version); + if (result != 1) + throw new Exception("Unknown Error"); + logger.LogInformation("Found new version for iOS: {VersionName}, check update for iOS ok", versionName); } } } - catch (Exception ex) - { - _logger.LogError(ex, $"Checking update for {os} failed."); - } - finally - { - var version = await _data.GetVersionAsync(os == OS.Android); - if (os == OS.Android) _currentVersionOfAndroid = version; - else _currentVersionOfIOS = version; - IsRunning = false; - } } - public enum OS + catch (Exception ex) + { + logger.LogError(ex, "Checking update for {OS} failed", os); + } + finally { - Android, - IOS + var version = await data.GetVersionAsync(os == OS.Android) ?? new Version(); + if (os == OS.Android) + _currentVersionOfAndroid = version; + else + _currentVersionOfIOS = version; + IsRunning = false; } } } diff --git a/ThuInfoWeb/Views/Home/Announce.cshtml b/ThuInfoWeb/Views/Home/Announce.cshtml index c53317e..8d8b67d 100644 --- a/ThuInfoWeb/Views/Home/Announce.cshtml +++ b/ThuInfoWeb/Views/Home/Announce.cshtml @@ -72,11 +72,11 @@
- @if ((int)ViewData["page"] != 1) + @if ((int)ViewData["page"]! != 1) { - 上一页 + 上一页 } - 下一页 + 下一页
- - - - @await RenderSectionAsync("Scripts", required: false) + + + + + +@await RenderSectionAsync("Scripts", false) - + \ No newline at end of file diff --git a/ThuInfoWeb/Views/Shared/_Layout.cshtml.css b/ThuInfoWeb/Views/Shared/_Layout.cshtml.css index a72cbea..947d5b5 100644 --- a/ThuInfoWeb/Views/Shared/_Layout.cshtml.css +++ b/ThuInfoWeb/Views/Shared/_Layout.cshtml.css @@ -2,47 +2,48 @@ for details on configuring this project to bundle and minify static web assets. */ a.navbar-brand { - white-space: normal; - text-align: center; - word-break: break-all; + white-space: normal; + text-align: center; + word-break: break-all; } a { - color: #0077cc; + color: #0077cc; } .btn-primary { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; } .nav-pills .nav-link.active, .nav-pills .show > .nav-link { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; } .border-top { - border-top: 1px solid #e5e5e5; + border-top: 1px solid #e5e5e5; } + .border-bottom { - border-bottom: 1px solid #e5e5e5; + border-bottom: 1px solid #e5e5e5; } .box-shadow { - box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); } button.accept-policy { - font-size: 1rem; - line-height: inherit; + font-size: 1rem; + line-height: inherit; } .footer { - position: absolute; - bottom: 0; - width: 100%; - white-space: nowrap; - line-height: 60px; + position: absolute; + bottom: 0; + width: 100%; + white-space: nowrap; + line-height: 60px; } diff --git a/ThuInfoWeb/Views/Shared/_ValidationScriptsPartial.cshtml b/ThuInfoWeb/Views/Shared/_ValidationScriptsPartial.cshtml index 5a16d80..660f00c 100644 --- a/ThuInfoWeb/Views/Shared/_ValidationScriptsPartial.cshtml +++ b/ThuInfoWeb/Views/Shared/_ValidationScriptsPartial.cshtml @@ -1,2 +1,2 @@  - + \ No newline at end of file diff --git a/ThuInfoWeb/Views/_ViewImports.cshtml b/ThuInfoWeb/Views/_ViewImports.cshtml index 06c5886..ac70218 100644 --- a/ThuInfoWeb/Views/_ViewImports.cshtml +++ b/ThuInfoWeb/Views/_ViewImports.cshtml @@ -1,3 +1,3 @@ @using ThuInfoWeb @using ThuInfoWeb.Models -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/ThuInfoWeb/Views/_ViewStart.cshtml b/ThuInfoWeb/Views/_ViewStart.cshtml index a5f1004..1af6e49 100644 --- a/ThuInfoWeb/Views/_ViewStart.cshtml +++ b/ThuInfoWeb/Views/_ViewStart.cshtml @@ -1,3 +1,3 @@ @{ Layout = "_Layout"; -} +} \ No newline at end of file diff --git a/ThuInfoWeb/appsettings.json b/ThuInfoWeb/appsettings.json index 5b9f2ea..b27b76e 100644 --- a/ThuInfoWeb/appsettings.json +++ b/ThuInfoWeb/appsettings.json @@ -15,19 +15,19 @@ "Secret": "" } }, - "InternalNetworkMode": false - /*"Kestrel": { + "InternalNetworkMode": false, + "Kestrel": { "Endpoints": { "Http": { - "Url": "http://localhost:80" - }, - "Https": { - "Url": "https://localhost:443", - "Certificate": { - "Path": "", - "Password": "" - } + "Url": "http://localhost:5000" } +// "Https": { +// "Url": "https://localhost:443", +// "Certificate": { +// "Path": "", +// "Password": "" +// } +// } } - }*/ + } } diff --git a/ThuInfoWeb/nlog.config b/ThuInfoWeb/nlog.config index 8bfd651..0ca27a2 100644 --- a/ThuInfoWeb/nlog.config +++ b/ThuInfoWeb/nlog.config @@ -1,33 +1,33 @@  - - - - - + + + + - - - - + + + + - - - + + + - - - - - + + + + + - - - + + + \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/arrow.svg b/ThuInfoWeb/wwwroot/assets/img/arrow.svg index 5fca909..98deb78 100644 --- a/ThuInfoWeb/wwwroot/assets/img/arrow.svg +++ b/ThuInfoWeb/wwwroot/assets/img/arrow.svg @@ -1,5 +1,5 @@ - + + stroke-linejoin="round"/> \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/classroom.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/classroom.svg index a21e642..354d287 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/classroom.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/classroom.svg @@ -1,16 +1,16 @@ - + + stroke-width="2.12121" stroke-linecap="round" stroke-linejoin="round"/> + stroke-linecap="round" stroke-linejoin="round"/> + stroke-linejoin="round"/> - + \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/ele.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/ele.svg index fbc5fc6..01e558b 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/ele.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/ele.svg @@ -1,13 +1,13 @@ + d="M8.75586 9.41052C8.75586 9.30107 8.84453 9.2124 8.95398 9.2124H25.5274C25.6369 9.2124 25.7256 9.30107 25.7256 9.41052V17.6973C25.7256 22.3833 21.9267 26.1821 17.2407 26.1821C12.5547 26.1821 8.75586 22.3833 8.75586 17.6973V9.41052Z" + fill="#9D60F8" stroke="#9D60F8" stroke-width="2.12121"/> + stroke-linecap="round" stroke-linejoin="round"/> + stroke-linecap="round" stroke-linejoin="round"/> + stroke-linejoin="round"/> + stroke-opacity="0.7" stroke-width="2.12121" stroke-linecap="round" stroke-linejoin="round"/> \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/eva.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/eva.svg index 9025b6e..30efe63 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/eva.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/eva.svg @@ -1,22 +1,22 @@ - + + d="M16.9691 14.5958C20.0931 14.5958 22.6256 12.0633 22.6256 8.93928C22.6256 5.81525 20.0931 3.28271 16.9691 3.28271C13.845 3.28271 11.3125 5.81525 11.3125 8.93928C11.3125 12.0633 13.845 14.5958 16.9691 14.5958Z" + fill="#9D60F8" fill-opacity="0.7"/> + d="M16.9691 15.1292C20.3877 15.1292 23.159 12.3579 23.159 8.93928C23.159 5.52069 20.3877 2.74938 16.9691 2.74938C13.5505 2.74938 10.7792 5.52069 10.7792 8.93928C10.7792 12.3579 13.5505 15.1292 16.9691 15.1292Z" + stroke="#9D60F8" stroke-opacity="0.7" stroke-width="1.06667" stroke-linecap="round" + stroke-linejoin="round"/> + d="M29.6967 31.5657C29.6967 24.5366 23.9985 18.8384 16.9695 18.8384C9.9404 18.8384 4.24219 24.5366 4.24219 31.5657" + stroke="#9D60F8" stroke-width="2.12121" stroke-linecap="round" stroke-linejoin="round"/> + stroke="#9D60F8" stroke-width="2.12121" stroke-linecap="round" stroke-linejoin="round"/> - + \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/exp.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/exp.svg index e780ebf..c73436d 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/exp.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/exp.svg @@ -1,15 +1,15 @@ + d="M2.82812 10.7774C2.82812 9.99639 3.46126 9.36328 4.24227 9.36328H29.6968C30.4778 9.36328 31.111 9.99639 31.111 10.7774V29.1613C31.111 29.9423 30.4778 30.5754 29.6968 30.5754H4.24227C3.46126 30.5754 2.82812 29.9423 2.82812 29.1613V10.7774Z" + fill="#9D60F8" stroke="#9D60F8" stroke-width="2.82828" stroke-linejoin="round"/> + stroke-linecap="round" stroke-linejoin="round"/> + stroke-linejoin="round"/> + stroke-linejoin="round"/> + stroke-linejoin="round"/> + stroke-linecap="round" stroke-linejoin="round"/> \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/lib.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/lib.svg index 159b396..0307c02 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/lib.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/lib.svg @@ -1,10 +1,10 @@ + d="M3.53516 4.96973H27.5756C27.5756 4.96973 30.4038 6.38387 30.4038 9.91922C30.4038 13.4546 27.5756 14.8687 27.5756 14.8687H3.53516C3.53516 14.8687 6.36344 13.4546 6.36344 9.91922C6.36344 6.38387 3.53516 4.96973 3.53516 4.96973Z" + fill="#9D60F8" fill-opacity="0.7" stroke="#9D60F8" stroke-width="2.12121" stroke-linecap="round" + stroke-linejoin="round"/> + d="M30.4038 20.5249H6.36344C6.36344 20.5249 3.53516 21.939 3.53516 25.4744C3.53516 29.0098 6.36344 30.4239 6.36344 30.4239H30.4038C30.4038 30.4239 27.5756 29.0098 27.5756 25.4744C27.5756 21.939 30.4038 20.5249 30.4038 20.5249Z" + fill="#9D60F8" fill-opacity="0.7" stroke="#9D60F8" stroke-width="2.12121" stroke-linecap="round" + stroke-linejoin="round"/> \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/news.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/news.svg index 2fcb31d..6cbe134 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/news.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/news.svg @@ -1,10 +1,11 @@ - + - - + d="M55.9429 8.75C55.9429 8.22927 55.5207 7.80714 55 7.80714H5C4.47927 7.80714 4.05714 8.22927 4.05714 8.75V46.25C4.05714 46.7707 4.47927 47.1929 5 47.1929H12.8071V52.5C12.8071 52.8268 12.9763 53.1302 13.2543 53.302C13.5323 53.4738 13.8794 53.4895 14.1717 53.3433L26.4726 47.1929H55C55.5207 47.1929 55.9429 46.7707 55.9429 46.25V8.75Z" + stroke="#9D60F8" stroke-opacity="0.9" stroke-width="1.88571" stroke-linecap="round" + stroke-linejoin="round"/> + + + stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/password.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/password.svg index a59d02d..806e90c 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/password.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/password.svg @@ -1,27 +1,27 @@ - + + stroke-linecap="round" stroke-linejoin="round"/> + stroke-linecap="round" stroke-linejoin="round"/> + stroke-linecap="round" stroke-linejoin="round"/> + stroke-linecap="round" stroke-linejoin="round"/> + d="M11.584 24.2227C11.584 24.2227 13.7052 26.3439 17.2406 26.3439C20.7759 26.3439 22.8971 24.2227 22.8971 24.2227" + stroke="#9D60F8" stroke-width="2.82828" stroke-linecap="round" stroke-linejoin="round"/> + stroke-width="2.82828" stroke-linecap="round" stroke-linejoin="round"/> + stroke-linejoin="round"/> + stroke-linejoin="round"/> - + \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/report.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/report.svg index daca653..76f4215 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/report.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/report.svg @@ -1,20 +1,20 @@ - + + stroke-linecap="round" stroke-linejoin="round"/> + stroke-linecap="round" stroke-linejoin="round"/> + stroke-linejoin="round"/> + stroke-linejoin="round"/> + stroke-linejoin="round"/> - + \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/schedule.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/schedule.svg index 15b55ed..622c7ff 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/schedule.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/schedule.svg @@ -1,17 +1,17 @@ - + + d="M6.25195 23.7495H53.752V49.9995C53.752 51.3803 52.6327 52.4995 51.252 52.4995H8.75195C7.37124 52.4995 6.25195 51.3803 6.25195 49.9995V23.7495Z" + fill="#9D60F8" stroke="#9D60F8" stroke-width="5" stroke-linejoin="round"/> + d="M6.25195 11.2495C6.25195 9.8688 7.37124 8.74951 8.75195 8.74951H51.252C52.6327 8.74951 53.752 9.8688 53.752 11.2495V23.7495H6.25195V11.2495Z" + stroke="#9D60F8" stroke-width="5" stroke-linejoin="round"/> + stroke-linejoin="round"/> - - - - + stroke-linejoin="round"/> + + + + \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/sport.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/sport.svg index 0a56c9a..d5b0335 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/sport.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/sport.svg @@ -1,13 +1,14 @@ + d="M21.1408 10.6264C23.0933 10.6264 24.6762 9.04352 24.6762 7.09102C24.6762 5.1385 23.0933 3.55566 21.1408 3.55566C19.1883 3.55566 17.6055 5.1385 17.6055 7.09102C17.6055 9.04352 19.1883 10.6264 21.1408 10.6264Z" + fill="#9D60F8" stroke="#9D60F8" stroke-width="2.12121" stroke-miterlimit="2"/> + d="M29.6271 17.6975C30.7986 17.6975 31.7483 16.7478 31.7483 15.5763C31.7483 14.4047 30.7986 13.4551 29.6271 13.4551C28.4555 13.4551 27.5059 14.4047 27.5059 15.5763C27.5059 16.7478 28.4555 17.6975 29.6271 17.6975Z" + fill="#9D60F8" fill-opacity="0.7"/> + stroke="#9D60F8" stroke-width="2.12121" stroke-miterlimit="2" stroke-linecap="round" stroke-linejoin="round"/> + d="M27.505 21.9397L17.4363 14.2114C16.8919 13.8084 16.1424 13.8508 15.6404 14.3033L12.2181 17.4357C11.8858 17.7397 11.9141 18.2629 12.2818 18.5316L19.0202 23.4599C19.5293 23.8347 19.7343 24.5064 19.508 25.1003L16.9838 31.8387" + stroke="#9D60F8" stroke-width="2.12121" stroke-miterlimit="2" stroke-linecap="round" + stroke-linejoin="round"/> \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/washer.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/washer.svg index 996e88c..42f2dba 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/washer.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/washer.svg @@ -1,27 +1,27 @@ - + + d="M27.1399 3.28271H7.34188C6.56087 3.28271 5.92773 3.91585 5.92773 4.69686V30.1514C5.92773 30.9324 6.56087 31.5655 7.34188 31.5655H27.1399C27.9209 31.5655 28.554 30.9324 28.554 30.1514V4.69686C28.554 3.91585 27.9209 3.28271 27.1399 3.28271Z" + stroke="#9D60F8" stroke-width="2.12121"/> + stroke-linejoin="round"/> + d="M20.0684 8.93961C20.8494 8.93961 21.4826 8.30648 21.4826 7.52547C21.4826 6.74446 20.8494 6.11133 20.0684 6.11133C19.2874 6.11133 18.6543 6.74446 18.6543 7.52547C18.6543 8.30648 19.2874 8.93961 20.0684 8.93961Z" + fill="#9D60F8"/> + d="M24.3126 8.93961C25.0936 8.93961 25.7267 8.30648 25.7267 7.52547C25.7267 6.74446 25.0936 6.11133 24.3126 6.11133C23.5316 6.11133 22.8984 6.74446 22.8984 7.52547C22.8984 8.30648 23.5316 8.93961 24.3126 8.93961Z" + fill="#9D60F8"/> + d="M17.2405 26.6163C19.974 26.6163 22.19 24.4003 22.19 21.6668C22.19 18.9332 19.974 16.7173 17.2405 16.7173C14.507 16.7173 12.291 18.9332 12.291 21.6668C12.291 24.4003 14.507 26.6163 17.2405 26.6163Z" + fill="#9D60F8" fill-opacity="0.7"/> + d="M17.2405 27.1496C20.2686 27.1496 22.7233 24.6949 22.7233 21.6668C22.7233 18.6387 20.2686 16.184 17.2405 16.184C14.2124 16.184 11.7577 18.6387 11.7577 21.6668C11.7577 24.6949 14.2124 27.1496 17.2405 27.1496Z" + stroke="#9D60F8" stroke-opacity="0.7" stroke-width="1.06667"/> - + \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/featuresicon/water.svg b/ThuInfoWeb/wwwroot/assets/img/featuresicon/water.svg index 8baeafa..31f0f72 100644 --- a/ThuInfoWeb/wwwroot/assets/img/featuresicon/water.svg +++ b/ThuInfoWeb/wwwroot/assets/img/featuresicon/water.svg @@ -1,11 +1,11 @@ + d="M9.8284 10.3535H21.8486C21.8486 10.3535 27.5052 13.2865 27.5052 20.1106C27.5052 26.9346 21.8486 30.1515 21.8486 30.1515H9.8284C9.8284 30.1515 4.87891 25.8939 4.87891 20.2525C4.87891 14.6111 9.8284 10.3535 9.8284 10.3535Z" + stroke="#9D60F8" stroke-width="2.12121" stroke-linecap="round" stroke-linejoin="round"/> + stroke-width="2.12121" stroke-linecap="round" stroke-linejoin="round"/> + stroke-width="2.12121" stroke-linecap="round" stroke-linejoin="round"/> + stroke-width="2.12121" stroke-linecap="round" stroke-linejoin="round"/> \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/gh.svg b/ThuInfoWeb/wwwroot/assets/img/gh.svg index 6111f2f..b152e57 100644 --- a/ThuInfoWeb/wwwroot/assets/img/gh.svg +++ b/ThuInfoWeb/wwwroot/assets/img/gh.svg @@ -1,9 +1,9 @@ - + + d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5229 6.47715 22 12 22C17.5229 22 22 17.5229 22 12C22 6.47715 17.5229 2 12 2ZM0 12C0 5.3726 5.3726 0 12 0C18.6274 0 24 5.3726 24 12C24 18.6274 18.6274 24 12 24C5.3726 24 0 18.6274 0 12Z" + fill="#666666"/> + d="M9.59064 22.7356C9.49394 22.6107 9.49394 21.4984 9.59064 19.3988C8.55474 19.4346 7.90024 19.3627 7.62714 19.1831C7.21754 18.9138 6.80744 18.0831 6.44359 17.4977C6.07974 16.9123 5.27214 16.8198 4.94604 16.6889C4.61993 16.558 4.53807 16.0246 5.84464 16.428C7.15124 16.8315 7.21494 17.9301 7.62714 18.187C8.03934 18.4439 9.02474 18.3315 9.47144 18.1257C9.91809 17.9199 9.88524 17.1537 9.96489 16.8501C10.0656 16.5668 9.71064 16.5039 9.70284 16.5016C9.26679 16.5016 6.97599 16.0034 6.34674 13.7851C5.71754 11.5668 6.52809 10.1168 6.96049 9.4935C7.24874 9.07795 7.22324 8.19235 6.88399 6.8366C8.11579 6.6792 9.06634 7.0669 9.73574 7.9998C9.73639 8.00515 10.6133 7.47835 11.9991 7.47835C13.385 7.47835 13.8767 7.90745 14.2561 7.9998C14.6355 8.09215 14.939 6.3668 17.2824 6.8366C16.7932 7.7982 16.3834 8.9998 16.6962 9.4935C17.0089 9.9872 18.2362 11.5571 17.4823 13.7851C16.9797 15.2704 15.9917 16.1759 14.5182 16.5016C14.3492 16.5555 14.2648 16.6425 14.2648 16.7625C14.2648 16.9426 14.4932 16.9622 14.8223 17.8056C15.0416 18.3679 15.0575 19.9738 14.8698 22.6233C14.3943 22.7443 14.0244 22.8256 13.7601 22.8671C13.2914 22.9408 12.7825 22.9821 12.2824 22.998C11.7824 23.014 11.6088 23.0122 10.9175 22.9478C10.4567 22.9049 10.0144 22.8342 9.59064 22.7356Z" + fill="#666666"/> \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/home.svg b/ThuInfoWeb/wwwroot/assets/img/home.svg index 62c0cd9..277e526 100644 --- a/ThuInfoWeb/wwwroot/assets/img/home.svg +++ b/ThuInfoWeb/wwwroot/assets/img/home.svg @@ -1,9 +1,9 @@ - - + + + stroke-linecap="round" stroke-linejoin="round"/> - + stroke-linejoin="round"/> + \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/logo.svg b/ThuInfoWeb/wwwroot/assets/img/logo.svg index 132aa87..292bf20 100644 --- a/ThuInfoWeb/wwwroot/assets/img/logo.svg +++ b/ThuInfoWeb/wwwroot/assets/img/logo.svg @@ -1,15 +1,15 @@ - + stroke-width="1.66667"/> + + d="M11.207 11.6616C11.207 11.6616 15.5389 13.9139 17.424 16.0243C18.3729 17.0865 19.0962 18.2537 19.0962 19.716C19.0962 21.1784 19.0962 22.2999 19.0962 22.2999" + stroke="white" stroke-width="2.83688" stroke-linecap="round"/> + d="M28.7422 11.6616C28.7422 11.6616 24.4103 13.9139 22.5252 16.0243C21.5763 17.0865 20.853 18.2537 20.853 19.716C20.853 21.1784 20.853 22.2999 20.853 22.2999" + stroke="white" stroke-width="2.83688" stroke-linecap="round"/> + stroke-linejoin="round"/> + stroke-linecap="round" stroke-linejoin="round"/> \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/news.svg b/ThuInfoWeb/wwwroot/assets/img/news.svg index 2fcb31d..6cbe134 100644 --- a/ThuInfoWeb/wwwroot/assets/img/news.svg +++ b/ThuInfoWeb/wwwroot/assets/img/news.svg @@ -1,10 +1,11 @@ - + - - + d="M55.9429 8.75C55.9429 8.22927 55.5207 7.80714 55 7.80714H5C4.47927 7.80714 4.05714 8.22927 4.05714 8.75V46.25C4.05714 46.7707 4.47927 47.1929 5 47.1929H12.8071V52.5C12.8071 52.8268 12.9763 53.1302 13.2543 53.302C13.5323 53.4738 13.8794 53.4895 14.1717 53.3433L26.4726 47.1929H55C55.5207 47.1929 55.9429 46.7707 55.9429 46.25V8.75Z" + stroke="#9D60F8" stroke-opacity="0.9" stroke-width="1.88571" stroke-linecap="round" + stroke-linejoin="round"/> + + + stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/schedule.svg b/ThuInfoWeb/wwwroot/assets/img/schedule.svg index 15b55ed..622c7ff 100644 --- a/ThuInfoWeb/wwwroot/assets/img/schedule.svg +++ b/ThuInfoWeb/wwwroot/assets/img/schedule.svg @@ -1,17 +1,17 @@ - + + d="M6.25195 23.7495H53.752V49.9995C53.752 51.3803 52.6327 52.4995 51.252 52.4995H8.75195C7.37124 52.4995 6.25195 51.3803 6.25195 49.9995V23.7495Z" + fill="#9D60F8" stroke="#9D60F8" stroke-width="5" stroke-linejoin="round"/> + d="M6.25195 11.2495C6.25195 9.8688 7.37124 8.74951 8.75195 8.74951H51.252C52.6327 8.74951 53.752 9.8688 53.752 11.2495V23.7495H6.25195V11.2495Z" + stroke="#9D60F8" stroke-width="5" stroke-linejoin="round"/> + stroke-linejoin="round"/> - - - - + stroke-linejoin="round"/> + + + + \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/assets/img/star.svg b/ThuInfoWeb/wwwroot/assets/img/star.svg index ca9b364..65b57c3 100644 --- a/ThuInfoWeb/wwwroot/assets/img/star.svg +++ b/ThuInfoWeb/wwwroot/assets/img/star.svg @@ -1,7 +1,8 @@ - - + + + + d="M23.9986 5L17.8856 17.4776L4 19.4911L14.0589 29.3251L11.6544 43L23.9986 36.4192L36.3454 43L33.9586 29.3251L44 19.4911L30.1913 17.4776L23.9986 5Z" + fill="#F5CE55" stroke="#F5CE55" stroke-width="3" stroke-linejoin="round"/> \ No newline at end of file diff --git a/ThuInfoWeb/wwwroot/css/site.css b/ThuInfoWeb/wwwroot/css/site.css index afcbf76..6059701 100644 --- a/ThuInfoWeb/wwwroot/css/site.css +++ b/ThuInfoWeb/wwwroot/css/site.css @@ -1,20 +1,20 @@ html { - font-size: 14px; + font-size: 14px; } @media (min-width: 768px) { - html { - font-size: 16px; - } + html { + font-size: 16px; + } } html { - position: relative; - min-height: 100%; + position: relative; + min-height: 100%; } body { - margin-bottom: 60px; + margin-bottom: 60px; } a.navbar-brand { diff --git a/ThuInfoWeb/wwwroot/download.html b/ThuInfoWeb/wwwroot/download.html index fc162b7..6ad9374 100644 --- a/ThuInfoWeb/wwwroot/download.html +++ b/ThuInfoWeb/wwwroot/download.html @@ -1,3 +1,137 @@ -THU Info + + + + THU Info + +
+流畅访问众多校园应用" name="description"> + + + + + +
+
+
+
+

THU Info

+ 安卓下载iOS下载 +
+
+
+ + + + + + + + diff --git a/ThuInfoWeb/wwwroot/help.html b/ThuInfoWeb/wwwroot/help.html index 50e8ada..6df8b3f 100644 --- a/ThuInfoWeb/wwwroot/help.html +++ b/ThuInfoWeb/wwwroot/help.html @@ -1,3 +1,162 @@ -THU Info + + + + THU Info + +

常见问题

首先请尝试使用强制重新登录,如果不行则重新登录账号。如果还是不行请发送反馈。

在某次更新中,我们加入了隐藏不常用功能的机制。如有需要,可以在设置中找到开关。如有需求,您也可以自行隐藏某些功能。

当您登录了学校官方的代码托管平台,即GitLab后,学校官方的系统可能会向您发送登录提醒邮件,这有可能是因为您使用了THU Info中的GitLab功能。还请确认是否本人操作以保证账户安全。

这个问题正在修复中~

主页中功能展示的是开发者已经确认在APP中添加的功能,但是有可能正在开发中而未正式上线(俗称画饼)。功能是否可用应以最新发布的APP版本为准,请您见谅。

+流畅访问众多校园应用" name="description"> + + + + + +
+

常见问题

+
+
+ +
+
+

+ 首先请尝试使用强制重新登录,如果不行则重新登录账号。如果还是不行请发送反馈。

+
+
+
+ +
+
+

+ 在某次更新中,我们加入了隐藏不常用功能的机制。如有需要,可以在设置中找到开关。如有需求,您也可以自行隐藏某些功能。

+
+
+
+
+ +
+
+

+ 当您登录了学校官方的代码托管平台,即GitLab后,学校官方的系统可能会向您发送登录提醒邮件,这有可能是因为您使用了THU + Info中的GitLab功能。还请确认是否本人操作以保证账户安全。

+
+
+
+ +
+
+

这个问题正在修复中~

+
+
+
+ +
+
+

+ 主页中功能展示的是开发者已经确认在APP中添加的功能,但是有可能正在开发中而未正式上线(俗称画饼)。功能是否可用应以最新发布的APP版本为准,请您见谅。

+
+
+
+
+
+
+ + + + + + + diff --git a/ThuInfoWeb/wwwroot/index.html b/ThuInfoWeb/wwwroot/index.html index af73fc1..08ac2b3 100644 --- a/ThuInfoWeb/wwwroot/index.html +++ b/ThuInfoWeb/wwwroot/index.html @@ -1,55 +1,278 @@ -THU Info + + + + THU Info + +

更好的校园信息门户

仅需一次登录,即可随时随地
流畅访问众多校园应用

- +
+
+

丰富功能,便利你的校园生活

+

整合信息门户最热门服务应用以及便利第三方服务,功能持续更新中

+
+
+
+
+

校园卡消费查询

+

随时随地查询当前校园卡余额、当月校园卡总收入、支出和每笔交易明细。

+
+
+
+
+

成绩单

+

可筛选课程类型(全部课程 / 必修限选),调整课表视图(按照时间 / + 分值排序)的成绩单,更有科目隐藏等有趣功能。

+
+
+
+
+

教室资源

+

随时随地查询当前各个教学楼的教室占用情况,以便随时随地开启自习讨论。

+
+
+
+
+

图书馆预约

+

支持查看楼层地图、座位插座状态显示、一键座位预约与取消。

+
+
+
+
+

宿舍电费

+

宿舍电费余额查询、快速充值,查看充值记录。

+
+
+
+
+

教学评估

+

手机端填写教评快速更高效。

+
+
+
+
+

清紫源泉

+

第三方功能,手机下单清紫源泉© 纯净水直送宿舍。

+
+
+
+
+

洗衣机状态查询

+

第三方功能,查询宿舍楼内洗衣机、洗鞋机、烘干机工作空闲状态,气定神闲洗衣去。

+
+
+
+
+

校园动态

+

同步获取各院系发布通知,支持搜索、收藏、订阅等操作。

+
+
+
+
+

计划

+

自动同步教务系统课程表,颜色区分,一目了然。支持手动添加个人计划,统一所有日程。

+
+
+
+
+
+

应用密码

+

支持数字、生物识别(指纹、Face ID)等方式设置应用锁,保护隐私数据安全不泄露。

+
+
+
+
+
+
+
+

我们如何保证信息安全?

+

THU Info 提取本地加密存储的账户密码,代替 浏览器 访问校园 WebVPN + 进行操作、获取返回数据。通过我们全新设计的界面以更加友好高效的方式展现给重视效率的用户。 

+

在此过程中,THU Info 仅会向学校信息门户官方服务器发送账户密码以及行为信息等数据。

+

项目代码已在GitHub开源,点击查看

+
+
+ +

丰富功能,便利你的校园生活

整合信息门户最热门服务应用以及便利第三方服务,功能持续更新中

校园卡消费查询

随时随地查询当前校园卡余额、当月校园卡总收入、支出和每笔交易明细。

成绩单

可筛选课程类型(全部课程 / 必修限选),调整课表视图(按照时间 / 分值排序)的成绩单,更有科目隐藏等有趣功能。

教室资源

随时随地查询当前各个教学楼的教室占用情况,以便随时随地开启自习讨论。

图书馆预约

支持查看楼层地图、座位插座状态显示、一键座位预约与取消。

宿舍电费

宿舍电费余额查询、快速充值,查看充值记录。

教学评估

手机端填写教评快速更高效。

清紫源泉

第三方功能,手机下单清紫源泉© 纯净水直送宿舍。

洗衣机状态查询

第三方功能,查询宿舍楼内洗衣机、洗鞋机、烘干机工作空闲状态,气定神闲洗衣去。

校园动态

同步获取各院系发布通知,支持搜索、收藏、订阅等操作。

计划

自动同步教务系统课程表,颜色区分,一目了然。支持手动添加个人计划,统一所有日程。

应用密码

支持数字、生物识别(指纹、Face ID)等方式设置应用锁,保护隐私数据安全不泄露。

我们如何保证信息安全?

THU Info 提取本地加密存储的账户密码,代替 浏览器 访问校园 WebVPN 进行操作、获取返回数据。通过我们全新设计的界面以更加友好高效的方式展现给重视效率的用户。 

在此过程中,THU Info 仅会向学校信息门户官方服务器发送账户密码以及行为信息等数据。

项目代码已在GitHub开源,点击查看

+
+ + + + + diff --git a/ThuInfoWeb/wwwroot/privacy-en.html b/ThuInfoWeb/wwwroot/privacy-en.html index 249bea5..17d9385 100644 --- a/ThuInfoWeb/wwwroot/privacy-en.html +++ b/ThuInfoWeb/wwwroot/privacy-en.html @@ -1,3 +1,144 @@ -THU Info + + + + THU Info + +

Privacy PolicyCheck out privacy policy in Chinese

Xun Sun built the THUInfo app as a Free app. This SERVICE is provided by Xun Sun at no cost and is intended for use as is.

This page is used to inform visitors regarding our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service.

If you choose to use our Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that we collect is used for providing and improving the Service. We will not use or share your information with anyone except as described in this Privacy Policy.

The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at THUInfo unless otherwise defined in this Privacy Policy.

Information Collection and Use

For a better experience, while using our Service, we may require you to provide us with certain personally identifiable information, including but not limited to User Credentials. The information that we request will be retained on your device and is not collected by us in any way.

Your User Credentials will only be sent directly to services provided by Tsinghua University.

Besides, this app uses a service built by THU Info maintainers for statistics and feedback support, which does not collect your sensitive data.

Cookies

Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent from APIs that the app accesses and are stored in your device’s internal memory.

This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.

Security

We value your trust in providing your Personal Information, thus we are striving to use commercially acceptable means to protect it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and we cannot guarantee its absolute security.

Links to Other Sites

This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by us. Therefore, we strongly advise you to review the Privacy Policy of these websites. We have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.

Children’s Privacy

These Services do not address anyone under the age of 13. We do not knowingly collect personally identifiable information from children under 13. In the case we discover that a child under 13 has provided us with personal information, we will immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact us so that we will be able to do necessary actions.

Changes to This Privacy Policy

We may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. We will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately after they are posted on this page.

Contact Us

If you have any questions or suggestions about this Privacy Policy, do not hesitate to contact us.

+流畅访问众多校园应用" name="description"> + + + + + +
+

Privacy PolicyCheck out privacy policy in + Chinese

+

Xun Sun built the THUInfo app as a Free app. This SERVICE is provided by Xun Sun at no cost and is intended + for use as is.

+

This page is used to inform visitors regarding our policies with the collection, use, and disclosure of + Personal Information if anyone decided to use our Service.

+

If you choose to use our Service, then you agree to the collection and use of information in relation to this + policy. The Personal Information that we collect is used for providing and improving the Service. We will + not use or share your information with anyone except as described in this Privacy Policy.

+

The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is + accessible at THUInfo unless otherwise defined in this Privacy Policy.

+

Information Collection and Use

+

For a better experience, while using our Service, we may require you to provide us with certain personally + identifiable information, including but not limited to User Credentials. The information that we request + will be retained on your device and is not collected by us in any way.

+

Your User Credentials will only be sent directly to services provided by Tsinghua University.

+

Besides, this app uses a service built by THU Info maintainers for statistics and feedback support, which + does not collect your sensitive data.

+

Cookies

+

Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These + are sent from APIs that the app accesses and are stored in your device’s internal memory.

+

This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries + that use “cookies” to collect information and improve their services. You have the option to either accept + or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our + cookies, you may not be able to use some portions of this Service.

+

Security

+

We value your trust in providing your Personal Information, thus we are striving to use commercially + acceptable means to protect it. But remember that no method of transmission over the internet, or method of + electronic storage is 100% secure and reliable, and we cannot guarantee its absolute security.

+
+

Links to Other Sites

+

This Service may contain links to other sites. If you click on a third-party link, you will be directed to + that site. Note that these external sites are not operated by us. Therefore, we strongly advise you to + review the Privacy Policy of these websites. We have no control over and assume no responsibility for the + content, privacy policies, or practices of any third-party sites or services.

+

Children’s Privacy

+

These Services do not address anyone under the age of 13. We do not knowingly collect personally identifiable + information from children under 13. In the case we discover that a child under 13 has provided us with + personal information, we will immediately delete this from our servers. If you are a parent or guardian and + you are aware that your child has provided us with personal information, please contact us so that we will + be able to do necessary actions.

+

Changes to This Privacy Policy

+

We may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically + for any changes. We will notify you of any changes by posting the new Privacy Policy on this page. These + changes are effective immediately after they are posted on this page.

+

Contact Us

+

If you have any questions or suggestions about this Privacy Policy, do not hesitate to contact us.

+
+ + + + + + + diff --git a/ThuInfoWeb/wwwroot/privacy.html b/ThuInfoWeb/wwwroot/privacy.html index b163338..7b95e57 100644 --- a/ThuInfoWeb/wwwroot/privacy.html +++ b/ThuInfoWeb/wwwroot/privacy.html @@ -1,3 +1,130 @@ -THU Info + + + + THU Info + +

隐私政策查看英文版隐私政策

Xun Sun 将 THUInfo 应用程序构建为免费应用程序。本服务由 Xun Sun 免费提供,旨在按原样使用。

此页面用于告知访客有关该应用收集、使用和披露个人信息的政策。

如果您选择使用我们的服务,则表示您同意与此政策相关的信息。我们收集的个人信息仅用于提供和改进服务。除非本隐私政策中所述,否则我们不会向任何人使用或分享您的信息。

本隐私政策中使用的术语与我们的条款和条件具有相同的含义,除非本隐私政策另有规定。

信息收集和使用

为了获得更好的体验,在使用我们的服务时,我们可能会要求您向我们提供某些个人身份信息,包括但不限于用户凭据。我们请求的信息将保留在您的设备上,不会以任何方式收集。

您的用户凭据只会直接发送至清华大学相关服务。

此外,该应用程序使用THU Info维护人员自主运维服务器实现用户统计和信息反馈,该服务不会收集您的任何敏感信息。

Cookies

Cookies 是包含少量数据的文件,通常用作匿名唯一标识符。这些是从应用程序访问的 API 发送的,并存储在设备的内存中。

本服务不明确使用这些 cookies。但是,该应用程序可能会使用第三方代码和使用 cookies 的库来收集信息并改进其服务。您可以选择拒绝这些 cookies,但将可能无法继续使用部分服务。

安全

我们非常重视您的信任,因此我们正在努力使用商业上可接受的方式来保护您的个人信息。但请记住,通过互联网传输或电子存储的方法永远不是 100% 安全可靠的,我们无法保证其绝对的安全性。

其他网站的链接

本服务可能包含指向其他站点的链接。如果您点击第三方链接,系统会将您定向到该网站。请注意,这些外部网站不由我们负责。因此,我们强烈建议您查看这些网站的隐私政策。我们无法控制任何第三方网站或服务的内容、隐私政策或行为,也不承担任何责任。

儿童隐私

这些服务不适用于 13 岁以下的任何人。我们不会有意收集 13 岁以下儿童的个人身份信息。在我们发现 13 岁以下的儿童向我们提供个人信息的情况下,我们会立即从我们的服务器上删除此信息。如果您是父母或监护人,并且您知道您的孩子向我们提供了个人信息,请与我们联系,以便我们能够采取必要的行动。

本隐私政策的变更

我们可能会不时更新我们的隐私政策。因此,建议您定期查看此页面以了解任何更改。我们将通过在此页面上发布新的隐私政策来进行任何更改。这些更改在此页面上发布后立即生效。

联系我们

如果您对本隐私政策有任何疑问或建议,请随时与我们联系

+流畅访问众多校园应用" name="description"> + + + + + +
+

隐私政策查看英文版隐私政策

+

Xun Sun 将 THUInfo 应用程序构建为免费应用程序。本服务由 Xun Sun 免费提供,旨在按原样使用。

+

此页面用于告知访客有关该应用收集、使用和披露个人信息的政策。

+

如果您选择使用我们的服务,则表示您同意与此政策相关的信息。我们收集的个人信息仅用于提供和改进服务。除非本隐私政策中所述,否则我们不会向任何人使用或分享您的信息。
+

+

本隐私政策中使用的术语与我们的条款和条件具有相同的含义,除非本隐私政策另有规定。

+

信息收集和使用

+

为了获得更好的体验,在使用我们的服务时,我们可能会要求您向我们提供某些个人身份信息,包括但不限于用户凭据。我们请求的信息将保留在您的设备上,不会以任何方式收集。
+

+

您的用户凭据只会直接发送至清华大学相关服务。

+

此外,该应用程序使用THU Info维护人员自主运维服务器实现用户统计和信息反馈,该服务不会收集您的任何敏感信息。
+

+

Cookies

+

Cookies 是包含少量数据的文件,通常用作匿名唯一标识符。这些是从应用程序访问的 API + 发送的,并存储在设备的内存中。

+

本服务不明确使用这些 cookies。但是,该应用程序可能会使用第三方代码和使用 cookies 的库来收集信息并改进其服务。您可以选择拒绝这些 + cookies,但将可能无法继续使用部分服务。

+

安全

+

我们非常重视您的信任,因此我们正在努力使用商业上可接受的方式来保护您的个人信息。但请记住,通过互联网传输或电子存储的方法永远不是 + 100% 安全可靠的,我们无法保证其绝对的安全性。

+

其他网站的链接

+

+ 本服务可能包含指向其他站点的链接。如果您点击第三方链接,系统会将您定向到该网站。请注意,这些外部网站不由我们负责。因此,我们强烈建议您查看这些网站的隐私政策。我们无法控制任何第三方网站或服务的内容、隐私政策或行为,也不承担任何责任。
+

+

儿童隐私

+

这些服务不适用于 13 岁以下的任何人。我们不会有意收集 13 岁以下儿童的个人身份信息。在我们发现 13 + 岁以下的儿童向我们提供个人信息的情况下,我们会立即从我们的服务器上删除此信息。如果您是父母或监护人,并且您知道您的孩子向我们提供了个人信息,请与我们联系,以便我们能够采取必要的行动。
+

+

本隐私政策的变更

+

我们可能会不时更新我们的隐私政策。因此,建议您定期查看此页面以了解任何更改。我们将通过在此页面上发布新的隐私政策来进行任何更改。这些更改在此页面上发布后立即生效。
+

+

联系我们

+

如果您对本隐私政策有任何疑问或建议,请随时与我们联系

+
+ + + + + + + diff --git a/ThuInfoWeb/wwwroot/sitemap.xml b/ThuInfoWeb/wwwroot/sitemap.xml index 16bfd3e..e73db53 100644 --- a/ThuInfoWeb/wwwroot/sitemap.xml +++ b/ThuInfoWeb/wwwroot/sitemap.xml @@ -1,5 +1,6 @@ - + /download.html