/* 设计计划(伪代码) - 详尽步骤说明 目标: - 为 SystemDatabaseService 类添加完整的文档注释(XML 注释), 以便在 IDE 中显示方法契约和用途。 - 保持现有实现逻辑不变,增加必要的参数检查(轻量级)以提高健壮性, 但不改变公开方法签名,确保兼容 ISystemDatabaseService。 - 将伪代码通过文件顶部注释包含在文件内(满足“先描述计划,再给代码”的要求)。 伪代码步骤: 1. 在文件顶部插入多行注释,包含本次修改的目的和每个主要动作的摘要。 2. 为类 `SystemDatabaseService` 添加 XML 文档注释,说明职责和依赖。 3. 为每个公共方法添加 XML 注释(summary, param, returns)。 - AddUserAsync(User user) - 验证 user 不为 null(如为 null 则抛出 ArgumentNullException) - 调用数据库插入并返回 user - DeleteUserAsync(int userId) - 直接调用 Deleteable 并返回是否删除到记录 - GetAllUsersAsync() - 返回所有用户列表 - GetUserByIdAsync(int userId) - 返回单一用户(InSingleAsync) - UpdateUserAsync(User user) - 验证 user 不为 null(如为 null 则抛出 ArgumentNullException) - 更新并返回 user - AuthenticateUserAsync(string username, string password) - 验证输入参数不为 null 或空(轻量级) - 使用 Queryable 查询并返回匹配用户(FirstAsync) - RecordProductionAsync(ProductionRecord record) - 验证 record 不为 null(如为 null 则抛出 ArgumentNullException) - 确保 record.Id 非空(Guid),插入记录 - ResetProductionAsync(string productName = null) - 如果 productName 为空,清空表,否则按产品名删除 - GetTotalProductionAsync(string productName, IEnumerable categories = null) - 构造查询,按类别可选过滤,求和数量并返回 - GetProductionStatsAsync(ProductionStatQuery query) - 验证 query 不为 null(如为 null 则抛出 ArgumentNullException) - 基于 query 参数构造查询(产品名、时间范围、类别) - 拉取列表后根据 GroupBy(hour/day/week/month/total)分组聚合 - 返回聚合结果集合 - 私有方法 FirstDayOfWeek(DateTime dt) - 计算给定日期的本周第一天(星期一),用于 week 分组 4. 确保所有新增的参数检查使用 .NET Framework 4.8 可用的异常类型。 5. 保持现有第三方 API(SqlSugar)调用不变,避免引入新依赖。 6. 生成的代码应能在 VS 中正确显示注释并通过编译(不改变方法签名)。 备注: - 不会尝试修改或新增接口方法签名(以确保与 ISystemDatabaseService 保持兼容)。 - 本次变更侧重于文档注释与轻量的输入验证,避免对业务逻辑做重大调整。 */ using SqlSugar; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using TeamAAS_VP.Interfaces; using TeamAAS_VP.Models; namespace TeamAAS_VP.Data { /// /// 系统数据库服务,基于 提供对应用数据的访问。 /// 包含用户增删改查、用户身份验证以及生产记录的写入与统计功能。 /// public class SystemDatabaseService : ISystemDatabaseService { private readonly ISqlSugarClient _db; /// /// 使用指定的 SqlSugar 客户端构造数据库服务实例。 /// /// 已配置的 实例。 public SystemDatabaseService(ISqlSugarClient db) { _db = db; } #region 用户 // User CRUD /// /// 添加新用户到数据库。 /// 对于 SqlSugar,User.Id 为数据库自增值,因此在此方法中不应设定 Id。 /// /// 要添加的用户对象,不能为空。 /// 返回传入的用户对象(Id 由数据库生成后会回写到该对象)。 /// 为 null 时抛出。 public async Task AddUserAsync(User user) { if (user == null) throw new ArgumentNullException(nameof(user)); // for SqlSugar User.Id is int identity, so do not set it here await _db.Insertable(user).ExecuteCommandAsync(); return user; } /// /// 根据用户 Id 删除用户。 /// /// 要删除的用户 Id。 /// 如果删除了至少一条记录则返回 true,否则返回 false。 public async Task DeleteUserAsync(int userId) { var r = await _db.Deleteable().In(userId).ExecuteCommandAsync(); return r > 0; } /// /// 获取所有用户。 /// /// 用户列表的异步枚举。 public async Task> GetAllUsersAsync() { return await _db.Queryable().ToListAsync(); } /// /// 根据用户 Id 查询单个用户。 /// /// 用户 Id。 /// 匹配的用户,如果未找到则返回 null(取决于 SqlSugar 的行为)。 public async Task GetUserByIdAsync(int userId) { return await _db.Queryable().InSingleAsync(userId); } /// /// 更新用户信息并返回更新后的对象。 /// /// 要更新的用户对象,不能为空。 /// 返回传入的用户对象。 /// 为 null 时抛出。 public async Task UpdateUserAsync(User user) { if (user == null) throw new ArgumentNullException(nameof(user)); var r = await _db.Updateable(user).ExecuteCommandAsync(); return user; } // User authentication /// /// 验证用户名和密码并返回匹配的用户。 /// 注意:当前实现直接以明文密码比较,请根据需要在调用侧或更高层添加哈希/盐策略。 /// /// 用户名,不能为空或空字符串。 /// 密码,不能为空或空字符串。 /// 匹配的用户对象,如果未找到则可能抛出或返回 null(取决于 SqlSugar 的 FirstAsync 行为)。 /// 为空时抛出。 public async Task AuthenticateUserAsync(string username, string password) { if (string.IsNullOrEmpty(username)) throw new ArgumentException("username is required", nameof(username)); if (string.IsNullOrEmpty(password)) throw new ArgumentException("password is required", nameof(password)); return await _db.Queryable().Where(u => u.UserName == username && u.UserPassword == password).FirstAsync(); } // User login / current user helpers /// /// 将指定用户设为当前用户(写入一条成功的登录记录)。 /// /// 要设为当前用户的用户 Id。 public async Task SetCurrentUserAsync(int userId) { // 验证用户存在 var user = await GetUserByIdAsync(userId); if (user == null) { throw new ArgumentException($"User with id {userId} not found", nameof(userId)); } // 记录一条成功的登录记录 var rec = new UserLoginRecord { UserId = userId, Success = true, Time = DateTime.UtcNow, Message = "Set as current user" }; await RecordUserLoginAsync(rec); } /// /// 获取最近一次成功登录的用户(视为当前用户)。 /// /// 若存在成功登录记录则返回对应用户,否则返回 null。 public Task GetCurrentUserAsync() { return Task.FromResult(GetCurrentUser()); } /// /// 获取当前用户 /// /// public User GetCurrentUser() { var recs = _db.Queryable() .Where(r => r.Success == true) .OrderBy(r => r.Time, OrderByType.Desc) .Take(1) .ToList(); var rec = recs.FirstOrDefault(); //如果没有登录记录则返回一个默认的用户,即第一个用户 if (rec == null) return _db.Queryable().First(); return _db.Queryable().InSingle(rec.UserId); } /// /// 记录一条用户登录记录(成功或失败)。 /// /// 登录记录对象,不能为空。 public async Task RecordUserLoginAsync(UserLoginRecord record) { if (record == null) throw new ArgumentNullException(nameof(record)); if (record.Time == default(DateTime)) record.Time = DateTime.UtcNow; await _db.Insertable(record).ExecuteCommandAsync(); } /// /// 获取指定用户最近的一条登录记录(无论是否成功)。 /// /// 用户 Id。 /// 最近的一条登录记录或 null。 public async Task GetLastUserLoginRecordAsync(int userId) { var recs = await _db.Queryable() .Where(r => r.UserId == userId) .OrderBy(r => r.Time, OrderByType.Desc) .Take(1) .ToListAsync(); return recs.FirstOrDefault(); } /// /// 将指定用户名标记为“记住”,并取消其他用户的记住标志。 /// /// 要记住的用户名。 public async Task SetRememberUserAsync(string username) { if (string.IsNullOrEmpty(username)) throw new ArgumentException("username is required", nameof(username)); // 使用事务先清除所有记住标志,再为指定用户设置记住 try { _db.Ado.BeginTran(); // 清除所有 await _db.Updateable().SetColumns(u => new User { IsRemember = false }).Where("1=1").ExecuteCommandAsync(); // 设置指定用户为记住 await _db.Updateable().SetColumns(u => new User { IsRemember = true }).Where(u => u.UserName == username).ExecuteCommandAsync(); _db.Ado.CommitTran(); } catch { try { _db.Ado.RollbackTran(); } catch { } throw; } } /// /// 清除所有用户的记住标志。 /// public async Task ClearRememberUserAsync() { await _db.Updateable().SetColumns(u => new User { IsRemember = false }).ExecuteCommandAsync(); } /// /// 获取当前被标记为记住的用户(如果存在)。 /// /// 被记住的用户或 null。 public async Task GetRememberedUserAsync() { return await _db.Queryable().Where(u => u.IsRemember == true).FirstAsync(); } #endregion #region 生产记录 // Production records /// /// 记录一次生产事件。如果 , /// 则会自动生成新的 Guid。 /// /// 生产记录对象,不能为空。 /// 异步任务。 /// 为 null 时抛出。 public async Task RecordProductionAsync(ProductionRecord record) { if (record == null) throw new ArgumentNullException(nameof(record)); // 如果未指定产生该记录的用户名,尝试使用当前登录用户 if (string.IsNullOrEmpty(record.UserName)) { try { var current = await GetCurrentUserAsync(); if (current != null) { record.UserName = current.UserName; } } catch { // 忽略获取当前用户时的错误,继续保存记录(保守策略) } } record.Id = record.Id == Guid.Empty ? Guid.NewGuid() : record.Id; await _db.Insertable(record).ExecuteCommandAsync(); } /// /// 重置生产记录。若未传入 ,则清空所有生产记录; /// 否则仅删除指定产品名的记录。 /// /// 可选的产品名过滤。 /// 异步任务。 public async Task ResetProductionAsync(string productName = null) { if (string.IsNullOrEmpty(productName)) { await _db.Deleteable().Where("1=1").ExecuteCommandAsync(); } else { await _db.Deleteable().Where(r => r.ProductName == productName).ExecuteCommandAsync(); } } /// /// 获取指定产品(和可选类别)在数据库中累计的产量总和(仅统计 Quantity > 0 的记录)。 /// /// 产品名,不能为空或空字符串。 /// 可选的类别集合,用于过滤。 /// 指定条件下的产量总和(整数)。 /// 为空时抛出。 public async Task GetTotalProductionAsync(string productName, IEnumerable categories = null) { if (string.IsNullOrEmpty(productName)) throw new ArgumentException("productName is required", nameof(productName)); var q = _db.Queryable().Where(r => r.ProductName == productName && r.Quantity > 0); if (categories != null && categories.Any()) { q = q.Where(r => categories.Contains(r.Category)); } var sum = await q.SumAsync(r => r.Quantity); return sum; } /// /// 获取生产统计结果,按指定时间范围、产品名和可选类别筛选, /// 并根据 (hour/day/week/month/total)进行分组聚合。 /// /// 生产统计查询对象,不能为空,必须包含 ProductName/Start/End 等。 /// 按指定分组粒度返回的统计结果集合。 /// 为 null 时抛出。 public async Task> GetProductionStatsAsync(ProductionStatQuery query) { if (query == null) throw new ArgumentNullException(nameof(query)); if (string.IsNullOrEmpty(query.ProductName)) throw new ArgumentException("ProductName is required in query", nameof(query)); var q = _db.Queryable().Where(r => r.ProductName == query.ProductName && r.Timestamp >= query.Start && r.Timestamp < query.End); if (query.Categories != null && query.Categories.Any()) { q = q.Where(r => query.Categories.Contains(r.Category)); } var list = await q.ToListAsync(); var results = new List(); switch ((query.GroupBy ?? "total").ToLower()) { case "hour": // group by hour var hours = list.GroupBy(r => new DateTime(r.Timestamp.Year, r.Timestamp.Month, r.Timestamp.Day, r.Timestamp.Hour, 0, 0)) .OrderBy(g => g.Key); foreach (var g in hours) { results.Add(new ProductionStatResult { PeriodStart = g.Key, PeriodEnd = g.Key.AddHours(1), Quantity = g.Sum(x => x.Quantity), ProductName = query.ProductName }); } break; case "day": var days = list.GroupBy(r => r.Timestamp.Date).OrderBy(g => g.Key); foreach (var g in days) { results.Add(new ProductionStatResult { PeriodStart = g.Key, PeriodEnd = g.Key.AddDays(1), Quantity = g.Sum(x => x.Quantity), ProductName = query.ProductName }); } break; case "week": // ISO week grouping by week start (Monday) var weeks = list.GroupBy(r => FirstDayOfWeek(r.Timestamp)).OrderBy(g => g.Key); foreach (var g in weeks) { results.Add(new ProductionStatResult { PeriodStart = g.Key, PeriodEnd = g.Key.AddDays(7), Quantity = g.Sum(x => x.Quantity), ProductName = query.ProductName }); } break; case "month": var months = list.GroupBy(r => new DateTime(r.Timestamp.Year, r.Timestamp.Month, 1)).OrderBy(g => g.Key); foreach (var g in months) { results.Add(new ProductionStatResult { PeriodStart = g.Key, PeriodEnd = g.Key.AddMonths(1), Quantity = g.Sum(x => x.Quantity), ProductName = query.ProductName }); } break; default: results.Add(new ProductionStatResult { PeriodStart = query.Start, PeriodEnd = query.End, Quantity = list.Sum(x => x.Quantity), ProductName = query.ProductName }); break; } return results; } /// /// 获取所有已知分类相加起来的今天的产能 /// /// /// public async Task GetTodayProductionAsync(string productName) { //今天的起始时间 var todayStart = DateTime.UtcNow.Date; //今天的结束时间 var todayEnd = DateTime.UtcNow.Date; todayEnd= todayEnd.AddDays(1); //按照产品名称和时间范围查询生产记录的总数 var total = await _db.Queryable() .Where(r => r.ProductName == productName && r.Timestamp >= todayStart && r.Timestamp < todayEnd) .SumAsync(r => r.Quantity); return total; } /// /// 获取所有已知分类相加起来的当前时间往前的一个小时的产能 /// /// /// public async Task GetLastHourProductionAsync(string productName) { var lastHourStart = DateTime.UtcNow.AddHours(-1); var lastHourEnd = DateTime.UtcNow; //按照产品名称和时间范围查询生产记录的总数 var total = await _db.Queryable() .Where(r => r.ProductName == productName && r.Timestamp >= lastHourStart && r.Timestamp < lastHourEnd) .SumAsync(r => r.Quantity); return total; } /// /// 获取当前月的产能 /// /// /// public Task GetCurrentMonthProductionAsync(string productName) { var now = DateTime.UtcNow; var monthStart = new DateTime(now.Year, now.Month, 1); var monthEnd = monthStart.AddMonths(1); //按照产品名称和时间范围查询生产记录的总数 return _db.Queryable() .Where(r => r.ProductName == productName && r.Timestamp >= monthStart && r.Timestamp < monthEnd) .SumAsync(r => r.Quantity); } /// /// 获取当前周的产能 /// /// /// public Task GetCurrentWeekProductionAsync(string productName) { var now = DateTime.UtcNow; var weekStart = FirstDayOfWeek(now); var weekEnd = weekStart.AddDays(7); //按照产品名称和时间范围查询生产记录的总数 return _db.Queryable() .Where(r => r.ProductName == productName && r.Timestamp >= weekStart && r.Timestamp < weekEnd) .SumAsync(r => r.Quantity); } /// /// 计算给定日期所属周的第一天(以星期一为一周的第一天)。 /// 返回结果为日期的 Date 部分(时间为 00:00:00)。 /// /// 输入日期。 /// 该日期对应周的周一日期(Date 部分)。 private DateTime FirstDayOfWeek(DateTime dt) { var diff = (7 + (dt.DayOfWeek - DayOfWeek.Monday)) % 7; return dt.Date.AddDays(-1 * diff); } /// /// 获取指定产品名称的总产能 /// /// /// public Task GetOverallProductionAsync(string productName) { return _db.Queryable() .Where(r => r.ProductName == productName) .SumAsync(r => r.Quantity); } /// /// 获取指定产品的所有已知分类。 /// /// 要查询分类的产品名称。 /// /// 一个字符串集合,包含该产品的所有分类名称;如果没有分类,返回空集合或 null(建议返回空集合)。 /// public async Task> GetProductCategoriesAsync(string productName) { var categories = await _db.Queryable() .Where(r => r.ProductName == productName) .Select(r => r.Category) .Distinct() .ToListAsync(); return categories; } /// /// 获取指定产品名称的所有记录按照分类的产能统计 /// /// /// public async Task> GetProductionByCategoriesAsync(string productName) { var list = await _db.Queryable() .Where(r => r.ProductName == productName) .ToListAsync(); var dict = list.GroupBy(r => r.Category) .ToDictionary(g => g.Key, g => g.Sum(r => r.Quantity)); return dict; } /// /// 获取指定产品名称的一个月内按照每天统计对应的类别和数量 /// /// /// public async Task>> GetMonthlyProductionByDayAndCategoryAsync(string productName) { var now = DateTime.UtcNow; var monthStart = new DateTime(now.Year, now.Month, 1); var monthEnd = monthStart.AddMonths(1); var list = await _db.Queryable() .Where(r => r.ProductName == productName && r.Timestamp >= monthStart && r.Timestamp < monthEnd) .ToListAsync(); var dict = new Dictionary>(); var days = list.GroupBy(r => r.Timestamp.Date).OrderBy(g => g.Key); foreach (var dayGroup in days) { var categoryDict = dayGroup.GroupBy(r => r.Category) .ToDictionary(g => g.Key, g => g.Sum(r => r.Quantity)); dict[dayGroup.Key] = categoryDict; } return dict; } /// /// 获取指定产品名称的一个星期内按照每天统计对应的类别和数量 /// /// /// public async Task>> GetWeeklyProductionByDayAndCategoryAsync(string productName) { var now = DateTime.UtcNow; var weekStart = FirstDayOfWeek(now); var weekEnd = weekStart.AddDays(7); var list = await _db.Queryable() .Where(r => r.ProductName == productName && r.Timestamp >= weekStart && r.Timestamp < weekEnd) .ToListAsync(); var dict = new Dictionary>(); var days = list.GroupBy(r => r.Timestamp.DayOfWeek).OrderBy(g => g.Key); foreach (var dayGroup in days) { var categoryDict = dayGroup.GroupBy(r => r.Category) .ToDictionary(g => g.Key, g => g.Sum(r => r.Quantity)); dict[(int)dayGroup.Key] = categoryDict; } return dict; } /// /// 获取指定产品名称的当天内按照每小时统计对应的类别和数量 /// /// /// public async Task>> GetDailyProductionByHourAndCategoryAsync(string productName) { var now = DateTime.UtcNow; var dayStart = now.Date; var dayEnd = dayStart.AddDays(1); var list = await _db.Queryable() .Where(r => r.ProductName == productName && r.Timestamp >= dayStart && r.Timestamp < dayEnd) .ToListAsync(); var dict = new Dictionary>(); var hours = list.GroupBy(r => r.Timestamp.Hour).OrderBy(g => g.Key); foreach (var hourGroup in hours) { var categoryDict = hourGroup.GroupBy(r => r.Category) .ToDictionary(g => g.Key, g => g.Sum(r => r.Quantity)); dict[hourGroup.Key] = categoryDict; } return dict; } #endregion #region 报警记录 // Alarm Records /// /// 记录一条报警记录。 /// /// 报警记录对象,不能为空。 public async Task RecordAlarmAsync(AlarmRecord alarm) { if (alarm == null) throw new ArgumentNullException(nameof(alarm)); if (alarm.Time == default(DateTime)) alarm.Time = DateTime.UtcNow; await _db.Insertable(alarm).ExecuteCommandAsync(); } /// /// 按时间范围和可选条件查询报警记录。 /// /// 起始时间(含)。 /// 结束时间(不含)。 /// 可选的报警编号过滤。 /// 可选的报警来源过滤。 /// 满足条件的报警记录集合。 public async Task> QueryAlarmsAsync(DateTime start, DateTime end, string code = null, string source = null) { var q = _db.Queryable().Where(a => a.Time >= start && a.Time < end); if (!string.IsNullOrEmpty(code)) q = q.Where(a => a.Code == code); if (!string.IsNullOrEmpty(source)) q = q.Where(a => a.Source == source); return await q.OrderBy(a => a.Time, OrderByType.Desc).ToListAsync(); } #endregion } }