using Microsoft.EntityFrameworkCore; using MilliSim.Common.Dtos; using MilliSim.Common.Entities; using MilliSim.Common.Models; using MilliSim.Data; using MilliSim.Services.Infrastructure; namespace MilliSim.Services; /// /// 授权服务(客户级授权:对客户授权后,该客户能运行所有项目) /// public class AuthorizationService : IAuthorizationService { private readonly MilliSimDbContext _db; private readonly ICurrentUserService _currentUser; private readonly IOperationLogService _operationLog; private readonly INotificationService _notification; public AuthorizationService( MilliSimDbContext db, ICurrentUserService currentUser, IOperationLogService operationLog, INotificationService notification) { _db = db; _currentUser = currentUser; _operationLog = operationLog; _notification = notification; } /// /// 获取授权列表(销售只能查看自己创建的客户的授权) /// public async Task>> GetAuthorizationsAsync(AuthorizationQueryRequest request) { // 自动将过期授权标记为 Expired await MarkExpiredAsync(); var query = from a in _db.Authorizations join u in _db.Users on a.UserId equals u.Id into ug from u in ug.DefaultIfEmpty() join au in _db.Users on a.AuthorizedBy equals au.Id into aug from au in aug.DefaultIfEmpty() select new { a, u, au }; // 销售数据隔离:销售只能查看自己创建的客户的授权 if (_currentUser.Role == UserRole.Sales) { query = query.Where(x => x.u.CreatedBy == _currentUser.UserId); } if (request.UserId.HasValue) { query = query.Where(x => x.a.UserId == request.UserId.Value); } if (request.Status.HasValue) { query = query.Where(x => x.a.Status == request.Status.Value); } if (!string.IsNullOrWhiteSpace(request.Keyword)) { var keyword = request.Keyword.Trim(); query = query.Where(x => (x.u != null && x.u.Username.Contains(keyword)) || (x.u != null && x.u.Nickname.Contains(keyword))); } var total = await query.LongCountAsync(); var items = await query .OrderByDescending(x => x.a.CreatedAt) .Skip((request.PageIndex - 1) * request.PageSize) .Take(request.PageSize) .Select(x => new AuthorizationDto { Id = x.a.Id, UserId = x.a.UserId, Username = x.u != null ? x.u.Username : string.Empty, UserNickname = x.u != null ? x.u.Nickname : string.Empty, AuthorizedBy = x.a.AuthorizedBy, AuthorizedByName = x.au != null ? x.au.Nickname : string.Empty, StartTime = x.a.StartTime, EndTime = x.a.EndTime, Status = x.a.Status, StatusName = x.a.Status.ToString(), CreatedAt = x.a.CreatedAt }) .ToListAsync(); return ApiResponse>.Success( PagedResult.Create(items, total, request.PageIndex, request.PageSize)); } /// /// 获取授权详情 /// public async Task> GetAuthorizationByIdAsync(long id) { await MarkExpiredAsync(); var entity = await _db.Authorizations .Include(a => a.User) .FirstOrDefaultAsync(a => a.Id == id); if (entity == null) { return ApiResponse.Fail("授权不存在", 404); } // 销售数据隔离 if (_currentUser.Role == UserRole.Sales) { var customer = await _db.Users.FirstOrDefaultAsync(u => u.Id == entity.UserId); if (customer == null || customer.CreatedBy != _currentUser.UserId) { return ApiResponse.Fail("无权查看该授权", 403); } } var authorizer = await _db.Users.FirstOrDefaultAsync(u => u.Id == entity.AuthorizedBy); var dto = MapToDto(entity, entity.User, authorizer); return ApiResponse.Success(dto); } /// /// 创建/重新授权(客户级授权):一个客户只能有一个授权记录。 /// 若客户已有授权(任意状态):Active 则续期(EndTime += days),Cancelled/Expired 则重置为 Active 重新开通。 /// 若客户无授权:创建新记录。 /// public async Task> CreateAuthorizationAsync(CreateAuthorizationRequest request) { if (request.Days <= 0) { request.Days = 365; } // 校验用户存在 var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == request.UserId); if (user == null) { return ApiResponse.Fail("用户不存在"); } // 销售数据隔离:销售只能为自己创建的客户授权 if (_currentUser.Role == UserRole.Sales && user.CreatedBy != _currentUser.UserId) { return ApiResponse.Fail("无权为该用户授权", 403); } await MarkExpiredAsync(); // 一个客户只能有一个授权记录:查询任意状态的现有授权 var existing = await _db.Authorizations .FirstOrDefaultAsync(a => a.UserId == request.UserId); var now = DateTime.Now; if (existing != null) { // 捕获原始状态,用于区分续期/重新授权的通知文案 var wasActive = existing.Status == AuthorizationStatus.Active; if (wasActive) { // 续期:在原 EndTime 基础上加天数(如果已过期则从当前时间开始) var baseTime = existing.EndTime > now ? existing.EndTime : now; existing.EndTime = baseTime.AddDays(request.Days); } else { // 重新授权:重置起止时间和状态(Cancelled / Expired → Active) existing.StartTime = now; existing.EndTime = now.AddDays(request.Days); existing.Status = AuthorizationStatus.Active; existing.ProjectId = null; // 客户级授权 } existing.AuthorizedBy = _currentUser.UserId; existing.UpdatedAt = now; await _db.SaveChangesAsync(); var actionLabel = wasActive ? "续期" : "重新授权"; await _operationLog.LogAsync("授权", actionLabel, $"用户 {user.Username} {actionLabel} {request.Days} 天,授权 ID: {existing.Id}"); // 通知被授权用户 try { var title = wasActive ? "授权已续期" : "授权已重新开通"; var content = wasActive ? $"您的客户级授权已续期 {request.Days} 天,新到期时间:{existing.EndTime:yyyy-MM-dd}。" : $"您的客户级授权已重新开通 {request.Days} 天,到期时间:{existing.EndTime:yyyy-MM-dd},可体验全部项目。"; await _notification.CreateNotificationAsync( NotificationType.Authorization, title, content, userId: user.Id, eventId: $"{(wasActive ? "auth-renewed" : "auth-reactivated")}-{existing.Id}-{existing.UpdatedAt:yyyyMMddHHmmss}"); } catch { // 通知发送失败不影响业务流程 } var authorizer = await _db.Users.FirstOrDefaultAsync(u => u.Id == existing.AuthorizedBy); var msg = wasActive ? "授权续期成功" : "授权重新开通成功"; return ApiResponse.Success(MapToDto(existing, user, authorizer), msg); } // 创建新授权(客户级授权,不关联项目) var entity = new Authorization { UserId = request.UserId, AuthorizedBy = _currentUser.UserId, StartTime = now, EndTime = now.AddDays(request.Days), Status = AuthorizationStatus.Active, ProjectId = null }; _db.Authorizations.Add(entity); await _db.SaveChangesAsync(); await _operationLog.LogAsync("授权", "创建", $"为用户 {user.Username} 创建客户级授权,{request.Days} 天,授权 ID: {entity.Id}"); // 通知被授权用户:授权开通 try { await _notification.CreateNotificationAsync( NotificationType.Authorization, "授权已开通", $"您已获得 {request.Days} 天客户级授权,可体验全部项目,到期时间:{entity.EndTime:yyyy-MM-dd}。", userId: user.Id, eventId: $"auth-created-{entity.Id}"); } catch { // 通知发送失败不影响业务流程 } var currentUser = await _db.Users.FirstOrDefaultAsync(u => u.Id == _currentUser.UserId); return ApiResponse.Success(MapToDto(entity, user, currentUser)); } /// /// 批量授权:一个客户一个授权记录,对每个 UserId 调用复用/新建逻辑。 /// Active 则续期,Cancelled/Expired 则重置为 Active 重新开通,无授权则新建。 /// public async Task BatchAuthorizeAsync(BatchAuthorizationRequest request) { if (request.UserIds == null || request.UserIds.Count == 0) { return ApiResponse.Fail("用户列表不能为空"); } if (request.Days <= 0) { request.Days = 365; } await MarkExpiredAsync(); var now = DateTime.Now; var successCount = 0; var skipCount = 0; foreach (var userId in request.UserIds.Distinct()) { var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == userId); if (user == null) { skipCount++; continue; } // 销售数据隔离 if (_currentUser.Role == UserRole.Sales && user.CreatedBy != _currentUser.UserId) { skipCount++; continue; } // 一个客户一个授权记录:查询任意状态的现有授权 var existing = await _db.Authorizations .FirstOrDefaultAsync(a => a.UserId == userId); if (existing != null) { var wasActive = existing.Status == AuthorizationStatus.Active; if (wasActive) { // 续期 var baseTime = existing.EndTime > now ? existing.EndTime : now; existing.EndTime = baseTime.AddDays(request.Days); } else { // 重新授权(Cancelled / Expired → Active) existing.StartTime = now; existing.EndTime = now.AddDays(request.Days); existing.Status = AuthorizationStatus.Active; existing.ProjectId = null; } existing.AuthorizedBy = _currentUser.UserId; existing.UpdatedAt = now; successCount++; // 通知用户 try { var title = wasActive ? "授权已续期" : "授权已重新开通"; var content = wasActive ? $"您的客户级授权已续期 {request.Days} 天,新到期时间:{existing.EndTime:yyyy-MM-dd}。" : $"您的客户级授权已重新开通 {request.Days} 天,到期时间:{existing.EndTime:yyyy-MM-dd},可体验全部项目。"; await _notification.CreateNotificationAsync( NotificationType.Authorization, title, content, userId: user.Id, eventId: $"{(wasActive ? "auth-renewed" : "auth-reactivated")}-{existing.Id}-{existing.UpdatedAt:yyyyMMddHHmmss}"); } catch { // 通知失败不影响业务 } } else { // 创建新授权(客户级授权,不关联项目) var entity = new Authorization { UserId = userId, AuthorizedBy = _currentUser.UserId, StartTime = now, EndTime = now.AddDays(request.Days), Status = AuthorizationStatus.Active, ProjectId = null }; _db.Authorizations.Add(entity); successCount++; // 先保存以获取 entity.Id try { await _db.SaveChangesAsync(); await _notification.CreateNotificationAsync( NotificationType.Authorization, "授权已开通", $"您已获得 {request.Days} 天客户级授权,可体验全部项目,到期时间:{entity.EndTime:yyyy-MM-dd}。", userId: user.Id, eventId: $"auth-created-{entity.Id}"); } catch { // 通知失败不影响业务 } } } await _db.SaveChangesAsync(); await _operationLog.LogAsync("授权", "批量授权", $"批量授权:成功 {successCount} 条,跳过 {skipCount} 条,{request.Days} 天"); return ApiResponse.Success($"批量授权完成,成功 {successCount} 条,跳过 {skipCount} 条"); } /// /// 续期授权(按授权 ID 续期) /// public async Task> RenewAuthorizationAsync(long id, int days) { if (days <= 0) { return ApiResponse.Fail("续期天数必须大于 0"); } await MarkExpiredAsync(); var entity = await _db.Authorizations .Include(a => a.User) .FirstOrDefaultAsync(a => a.Id == id); if (entity == null) { return ApiResponse.Fail("授权不存在", 404); } // 销售数据隔离 if (_currentUser.Role == UserRole.Sales) { var customer = await _db.Users.FirstOrDefaultAsync(u => u.Id == entity.UserId); if (customer == null || customer.CreatedBy != _currentUser.UserId) { return ApiResponse.Fail("无权操作该授权", 403); } } // 仅 Active 状态的授权可续期;Cancelled/Expired 请使用重新授权功能 if (entity.Status != AuthorizationStatus.Active) { var hint = entity.Status == AuthorizationStatus.Cancelled ? "已取消的授权请使用重新授权功能" : "已过期的授权请使用重新授权功能"; return ApiResponse.Fail(hint); } // 续期:在原 EndTime 基础上加天数(如果已过期则从当前时间开始) var baseTime = entity.EndTime > DateTime.Now ? entity.EndTime : DateTime.Now; entity.EndTime = baseTime.AddDays(days); entity.Status = AuthorizationStatus.Active; entity.UpdatedAt = DateTime.Now; await _db.SaveChangesAsync(); var authorizer = await _db.Users.FirstOrDefaultAsync(u => u.Id == entity.AuthorizedBy); await _operationLog.LogAsync("授权", "续期", $"授权 ID: {id} 续期 {days} 天"); // 通知被授权用户:续期 try { await _notification.CreateNotificationAsync( NotificationType.Authorization, "授权已续期", $"您的客户级授权已续期 {days} 天,新到期时间:{entity.EndTime:yyyy-MM-dd}。", userId: entity.UserId, eventId: $"auth-renewed-{entity.Id}-{entity.UpdatedAt:yyyyMMddHHmmss}"); } catch { // 通知失败不影响业务 } return ApiResponse.Success(MapToDto(entity, entity.User, authorizer), "续期成功"); } /// /// 取消授权(仅 Active 状态可取消) /// public async Task CancelAuthorizationAsync(long id) { var entity = await _db.Authorizations.FirstOrDefaultAsync(a => a.Id == id); if (entity == null) { return ApiResponse.Fail("授权不存在", 404); } // 销售数据隔离 if (_currentUser.Role == UserRole.Sales) { var customer = await _db.Users.FirstOrDefaultAsync(u => u.Id == entity.UserId); if (customer == null || customer.CreatedBy != _currentUser.UserId) { return ApiResponse.Fail("无权操作该授权", 403); } } // 仅 Active 状态可取消;已取消/已过期的不可重复取消 if (entity.Status == AuthorizationStatus.Cancelled) { return ApiResponse.Fail("该授权已取消,无需重复操作"); } if (entity.Status == AuthorizationStatus.Expired) { return ApiResponse.Fail("该授权已过期,请使用重新授权功能"); } entity.Status = AuthorizationStatus.Cancelled; entity.UpdatedAt = DateTime.Now; await _db.SaveChangesAsync(); await _operationLog.LogAsync("授权", "取消", $"取消授权 ID: {id}"); // 通知被授权用户:授权已取消 try { await _notification.CreateNotificationAsync( NotificationType.Authorization, "授权已取消", "您的客户级授权已被取消。如有疑问请联系销售或客服。", userId: entity.UserId, eventId: $"auth-cancelled-{entity.Id}"); } catch { // 通知失败不影响业务 } return ApiResponse.Success("授权已取消"); } /// /// 检查用户是否有任何有效授权(客户级授权,不检查项目) /// public async Task> CheckAuthorizationAsync(long userId) { await MarkExpiredAsync(); var hasAuth = await _db.Authorizations .AnyAsync(a => a.UserId == userId && a.Status == AuthorizationStatus.Active && a.EndTime > DateTime.Now); return ApiResponse.Success(hasAuth); } /// /// 获取当前客户的授权列表 /// public async Task>> GetMyAuthorizationsAsync() { await MarkExpiredAsync(); var userId = _currentUser.UserId; var query = from a in _db.Authorizations join u in _db.Users on a.UserId equals u.Id into ug from u in ug.DefaultIfEmpty() join au in _db.Users on a.AuthorizedBy equals au.Id into aug from au in aug.DefaultIfEmpty() where a.UserId == userId select new { a, u, au }; var items = await query .OrderByDescending(x => x.a.CreatedAt) .Select(x => new AuthorizationDto { Id = x.a.Id, UserId = x.a.UserId, Username = x.u != null ? x.u.Username : string.Empty, UserNickname = x.u != null ? x.u.Nickname : string.Empty, AuthorizedBy = x.a.AuthorizedBy, AuthorizedByName = x.au != null ? x.au.Nickname : string.Empty, StartTime = x.a.StartTime, EndTime = x.a.EndTime, Status = x.a.Status, StatusName = x.a.Status.ToString(), CreatedAt = x.a.CreatedAt }) .ToListAsync(); return ApiResponse>.Success(items); } /// /// 自动将过期授权标记为 Expired(EndTime < Now 且 Status == Active),并通知用户 /// private async Task MarkExpiredAsync() { var now = DateTime.Now; var expired = await _db.Authorizations .Where(a => a.Status == AuthorizationStatus.Active && a.EndTime < now) .ToListAsync(); if (expired.Count > 0) { foreach (var item in expired) { item.Status = AuthorizationStatus.Expired; item.UpdatedAt = now; // 通知用户:授权已过期 try { await _notification.CreateNotificationAsync( NotificationType.Authorization, "授权已过期", "您的客户级授权已过期,请联系销售续期后继续体验项目。", userId: item.UserId, eventId: $"auth-expired-{item.Id}"); } catch { // 通知失败不影响业务 } } await _db.SaveChangesAsync(); } } /// /// 映射为 DTO(不再映射项目字段) /// private static AuthorizationDto MapToDto(Authorization entity, User? user, User? authorizer) { return new AuthorizationDto { Id = entity.Id, UserId = entity.UserId, Username = user?.Username ?? string.Empty, UserNickname = user?.Nickname ?? string.Empty, AuthorizedBy = entity.AuthorizedBy, AuthorizedByName = authorizer?.Nickname ?? string.Empty, StartTime = entity.StartTime, EndTime = entity.EndTime, Status = entity.Status, StatusName = entity.Status.ToString(), CreatedAt = entity.CreatedAt }; } }