diff --git a/Src/CodeSpirit.ConfigCenter/Program.cs b/Src/CodeSpirit.ConfigCenter/Program.cs index ab36694ea5c2344d21547f8df6d18585ec9e2ed0..dbc4a72d633e81b4e15befd14c6380d2616e59bf 100644 --- a/Src/CodeSpirit.ConfigCenter/Program.cs +++ b/Src/CodeSpirit.ConfigCenter/Program.cs @@ -1,7 +1,8 @@ Console.OutputEncoding = System.Text.Encoding.UTF8; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); - +//获取对 RabbitMQ 消息代理的引用 +builder.AddRabbitMQClient("rabbitmq"); // 使用 ConfigCenter 扩展方法注册所有服务 builder.AddConfigCenter(); diff --git a/Src/CodeSpirit.Core/CodeSpirit.Core.csproj b/Src/CodeSpirit.Core/CodeSpirit.Core.csproj index eabb21d871609e256fbe519dae14b72fa2bcad9a..c6ab74c712709b81afa5ae0a172f367615fb218a 100644 --- a/Src/CodeSpirit.Core/CodeSpirit.Core.csproj +++ b/Src/CodeSpirit.Core/CodeSpirit.Core.csproj @@ -4,4 +4,8 @@ net9.0 disable + + + + diff --git a/Src/CodeSpirit.Core/RabbitEvent.cs b/Src/CodeSpirit.Core/RabbitEvent.cs new file mode 100644 index 0000000000000000000000000000000000000000..85ef888a38cd5cdf716d974ab36eeebbb7240fec --- /dev/null +++ b/Src/CodeSpirit.Core/RabbitEvent.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json.Linq; + +namespace CodeSpirit.Core +{ + public class RabbitEvent + { + /// + /// 名字 + /// + public string EventName { get; set; } = string.Empty; + + /// + /// 数据 + /// + public JToken EventData { get; set; } + } +} diff --git a/Src/CodeSpirit.ExamApi/CodeSpirit.ExamApi.csproj b/Src/CodeSpirit.ExamApi/CodeSpirit.ExamApi.csproj index 256105dc121b940398fbb3e255a5fad087b25308..039440df81e0ee7eb9c87856362f592082d028e0 100644 --- a/Src/CodeSpirit.ExamApi/CodeSpirit.ExamApi.csproj +++ b/Src/CodeSpirit.ExamApi/CodeSpirit.ExamApi.csproj @@ -11,6 +11,7 @@ + diff --git a/Src/CodeSpirit.ExamApi/Controllers/StudentGroupsController.cs b/Src/CodeSpirit.ExamApi/Controllers/StudentGroupsController.cs index 86574e24d9b710b431e6d95921f60228493a4330..924f0771376c2d941b9b7da292e4ebc7630bea97 100644 --- a/Src/CodeSpirit.ExamApi/Controllers/StudentGroupsController.cs +++ b/Src/CodeSpirit.ExamApi/Controllers/StudentGroupsController.cs @@ -2,8 +2,10 @@ using CodeSpirit.Core.Attributes; using CodeSpirit.Core.Dtos; using CodeSpirit.ExamApi.Data.Models; using CodeSpirit.ExamApi.Dtos.StudentGroup; +using CodeSpirit.ExamApi.Services.Implementations; using CodeSpirit.ExamApi.Services.Interfaces; using CodeSpirit.Shared.Dtos.Common; +using LinqKit; using Microsoft.AspNetCore.Mvc; using System.Linq.Expressions; @@ -17,6 +19,7 @@ namespace CodeSpirit.ExamApi.Controllers; public class StudentGroupsController : ApiControllerBase { private readonly IStudentGroupService _studentGroupService; + private readonly IStudentService _studentService; private readonly ILogger _logger; /// @@ -26,13 +29,15 @@ public class StudentGroupsController : ApiControllerBase /// 日志记录器 public StudentGroupsController( IStudentGroupService studentGroupService, - ILogger logger) + ILogger logger, + IStudentService studentService) { ArgumentNullException.ThrowIfNull(studentGroupService); ArgumentNullException.ThrowIfNull(logger); _studentGroupService = studentGroupService; _logger = logger; + _studentService = studentService; } /// @@ -100,6 +105,11 @@ public class StudentGroupsController : ApiControllerBase [DisplayName("删除考生组")] public async Task> DeleteStudentGroup(long id) { + var falg = await _studentService.JudgeWhetherStudentByGroupIdAsync(new List { id }); + if (falg) + { + return BadResponse("该考生组下有考生,无法删除!"); + } await _studentGroupService.DeleteAsync(id); return SuccessResponse(); } @@ -116,6 +126,12 @@ public class StudentGroupsController : ApiControllerBase { ArgumentNullException.ThrowIfNull(request); + var falg = await _studentService.JudgeWhetherStudentByGroupIdAsync(request.Ids); + if (falg) + { + return BadResponse("该考生组下有考生,无法删除!"); + } + (int successCount, List failedIds) = await _studentGroupService.BatchDeleteAsync(request.Ids); return failedIds.Any() @@ -177,7 +193,7 @@ public class StudentGroupsController : ApiControllerBase /// 学生组列表 [HttpGet("select")] [DisplayName("获取考生组选择列表")] - public async Task>>>> GetStudentGroupsForSelect([FromQuery]bool hasNoGroup = false) + public async Task>>>> GetStudentGroupsForSelect([FromQuery] bool hasNoGroup = false) { var groups = await _studentGroupService.GetAllActiveGroupsAsync(); var options = groups.Select(p => new OptionDto() { Id = p.Id, Name = p.Name }).ToList(); diff --git a/Src/CodeSpirit.ExamApi/Controllers/StudentsController.cs b/Src/CodeSpirit.ExamApi/Controllers/StudentsController.cs index 0aaf6e4b0b89e2efc47cb4bbaa2b286b476912d3..adc7bc7dbfb526ffbc32c31dd8ef4685b64528b1 100644 --- a/Src/CodeSpirit.ExamApi/Controllers/StudentsController.cs +++ b/Src/CodeSpirit.ExamApi/Controllers/StudentsController.cs @@ -1,9 +1,13 @@ using CodeSpirit.Core.Attributes; using CodeSpirit.Core.Dtos; +using CodeSpirit.ExamApi.Data.Models; using CodeSpirit.ExamApi.Dtos.Student; using CodeSpirit.ExamApi.Services.Interfaces; using CodeSpirit.Shared.Dtos.Common; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Text; namespace CodeSpirit.ExamApi.Controllers; @@ -16,7 +20,8 @@ public class StudentsController : ApiControllerBase { private readonly IStudentService _studentService; private readonly ILogger _logger; - + private readonly RabbitMQ.Client.IConnection _connection; + /// /// 初始化考生管理控制器 /// @@ -24,15 +29,17 @@ public class StudentsController : ApiControllerBase /// 日志记录器 public StudentsController( IStudentService studentService, - ILogger logger) + ILogger logger, + RabbitMQ.Client.IConnection connection) { ArgumentNullException.ThrowIfNull(studentService); ArgumentNullException.ThrowIfNull(logger); - + _studentService = studentService; _logger = logger; + _connection = connection; } - + /// /// 获取考生列表 /// @@ -45,7 +52,7 @@ public class StudentsController : ApiControllerBase var result = await _studentService.GetStudentsAsync(queryDto); return SuccessResponse(result); } - + /// /// 导出考生列表 /// @@ -59,16 +66,16 @@ public class StudentsController : ApiControllerBase const int MaxExportLimit = 10000; // 最大导出数量限制 queryDto.PerPage = MaxExportLimit; queryDto.Page = 1; - + // 获取考生数据 var result = await _studentService.GetStudentsAsync(queryDto); - + // 如果数据为空则返回错误信息 - return result.Items.Count == 0 - ? BadResponse>("没有数据可供导出") + return result.Items.Count == 0 + ? BadResponse>("没有数据可供导出") : SuccessResponse(result); } - + /// /// 获取考生详情 /// @@ -79,11 +86,11 @@ public class StudentsController : ApiControllerBase public async Task>> GetStudent(long id) { if (id <= 0) return BadRequest("无效的ID"); - + var result = await _studentService.GetAsync(id); return SuccessResponse(result); } - + /// /// 创建考生 /// @@ -96,7 +103,7 @@ public class StudentsController : ApiControllerBase var result = await _studentService.CreateAsync(createDto); return SuccessResponse(result); } - + /// /// 更新考生信息 /// @@ -110,7 +117,7 @@ public class StudentsController : ApiControllerBase await _studentService.UpdateAsync(id, updateDto); return SuccessResponse(); } - + /// /// 删除考生 /// @@ -121,10 +128,20 @@ public class StudentsController : ApiControllerBase [DisplayName("删除考生")] public async Task> DeleteStudent(long id) { + var student = await _studentService.GetAsync(id); await _studentService.DeleteAsync(id); + var channel = _connection.CreateModel(); + var enentObj = new RabbitEvent() + { + EventName = "StudentDeleted", + EventData = JToken.FromObject(new List { student.UserId }) + }; + var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(enentObj)); + channel.QueueDeclare(queue: "ExamEvents", durable: false, exclusive: false, autoDelete: false, arguments: null); + channel.BasicPublish(exchange: string.Empty, routingKey: "ExamEvents", mandatory: false, basicProperties: null, body: body); return SuccessResponse(); } - + /// /// 批量导入考生 /// @@ -135,13 +152,13 @@ public class StudentsController : ApiControllerBase public async Task> BatchImport([FromBody] BatchImportDtoBase importDto) { ArgumentNullException.ThrowIfNull(importDto); - + var result = await _studentService.BatchImportAsync(importDto.ImportData); return result.failedIds.Any() ? SuccessResponse($"成功导入 {result.successCount} 个考生,但以下考生导入失败: {string.Join(", ", result.failedIds)}") : SuccessResponse($"成功导入 {result.successCount} 个考生!"); } - + /// /// 批量删除考生 /// @@ -153,15 +170,27 @@ public class StudentsController : ApiControllerBase public async Task> BatchDelete([FromBody] BatchOperationDto request) { ArgumentNullException.ThrowIfNull(request); - + (int successCount, List failedIds) = await _studentService.BatchDeleteAsync(request.Ids); - + + List remainingIds = request.Ids.Except(failedIds).ToList(); + + var channel = _connection.CreateModel(); + var enentObj = new RabbitEvent() + { + EventName = "StudentDeleted", + EventData = JToken.FromObject(remainingIds) + }; + var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(enentObj)); + channel.QueueDeclare(queue: "ExamEvents", durable: false, exclusive: false, autoDelete: false, arguments: null); + channel.BasicPublish(exchange: string.Empty, routingKey: "ExamEvents", mandatory: false, basicProperties: null, body: body); + return failedIds.Any() ? SuccessResponse($"成功删除 {successCount} 个考生,但以下考生删除失败: {string.Join(", ", failedIds)}") : SuccessResponse($"成功删除 {successCount} 个考生!"); } - - + + /// /// 通过学号查询考生 /// @@ -172,11 +201,11 @@ public class StudentsController : ApiControllerBase public async Task>> GetByStudentNumber(string studentNumber) { if (string.IsNullOrEmpty(studentNumber)) return BadRequest("学号不能为空"); - + var result = await _studentService.GetByStudentNumberAsync(studentNumber); return SuccessResponse(result); } - + /// /// 通过用户ID查询考生 /// @@ -189,7 +218,7 @@ public class StudentsController : ApiControllerBase var result = await _studentService.GetByUserIdAsync(userId); return SuccessResponse(result); } - + /// /// 批量分配考生到考生组 /// @@ -201,11 +230,11 @@ public class StudentsController : ApiControllerBase public async Task> BatchAssignGroups([FromBody] BatchAssignGroupsDto request) { ArgumentNullException.ThrowIfNull(request); - + (int successCount, List failedIds) = await _studentService.BatchAssignGroupsAsync(request.Ids, request.GroupIds); - + return failedIds.Any() ? SuccessResponse($"成功分配 {successCount} 个考生到考生组,但以下考生分配失败: {string.Join(", ", failedIds)}") : SuccessResponse($"成功分配 {successCount} 个考生到考生组!"); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Src/CodeSpirit.ExamApi/Program.cs b/Src/CodeSpirit.ExamApi/Program.cs index 66c6c1b4ecf0881c992478852055b1b9076be69d..63bba1ca270ef02776a89e53f4083bc5a177ee9c 100644 --- a/Src/CodeSpirit.ExamApi/Program.cs +++ b/Src/CodeSpirit.ExamApi/Program.cs @@ -7,6 +7,8 @@ using CodeSpirit.Shared.DistributedLock; Console.OutputEncoding = Encoding.UTF8; var builder = WebApplication.CreateBuilder(args); +//获取对 RabbitMQ 消息代理的引用 +builder.AddRabbitMQClient("rabbitmq"); builder.AddExam(); // 添加AI题目生成服务 - 使用Extensions命名空间下的方法 diff --git a/Src/CodeSpirit.ExamApi/Services/Implementations/StudentService.cs b/Src/CodeSpirit.ExamApi/Services/Implementations/StudentService.cs index d37f5e1879557681291513ecc64334d8f8ac1f1e..514b1206994d36b15a1389c89e180c3edaf706a4 100644 --- a/Src/CodeSpirit.ExamApi/Services/Implementations/StudentService.cs +++ b/Src/CodeSpirit.ExamApi/Services/Implementations/StudentService.cs @@ -514,4 +514,10 @@ public class StudentService : BaseCRUDIService JudgeWhetherStudentByGroupIdAsync(List groupIds) + { + return await Repository.CreateQuery() + .AnyAsync(s => s.StudentGroups.Any(g => groupIds.Contains(g.StudentGroupId))); + } } \ No newline at end of file diff --git a/Src/CodeSpirit.ExamApi/Services/Interfaces/IStudentService.cs b/Src/CodeSpirit.ExamApi/Services/Interfaces/IStudentService.cs index 18fa62575d886dfeee2b9849c8ec3b870990e74e..bce03892d5fe069ca9421b15cfa79b831c03e7ae 100644 --- a/Src/CodeSpirit.ExamApi/Services/Interfaces/IStudentService.cs +++ b/Src/CodeSpirit.ExamApi/Services/Interfaces/IStudentService.cs @@ -33,4 +33,6 @@ public interface IStudentService : IBaseCRUDIService考生组ID列表 /// 成功数量和失败的ID列表 Task<(int successCount, List failedIds)> BatchAssignGroupsAsync(List studentIds, List groupIds); + + Task JudgeWhetherStudentByGroupIdAsync(List groupIds); } \ No newline at end of file diff --git a/Src/CodeSpirit.IdentityApi/Program.cs b/Src/CodeSpirit.IdentityApi/Program.cs index 5bf7ce9aa88e48f8cd63da622f66c8334ae2dd63..2b9c423aefd165828b96a691a2be236b48173d07 100644 --- a/Src/CodeSpirit.IdentityApi/Program.cs +++ b/Src/CodeSpirit.IdentityApi/Program.cs @@ -11,6 +11,7 @@ builder.AddIdentityApiServices(); // 注册JWT服务 builder.Services.AddScoped(); +builder.Services.AddHostedService(); // 注册登录日志仓储 builder.Services.AddScoped(); diff --git a/Src/CodeSpirit.IdentityApi/RabbitMQProcessingJob.cs b/Src/CodeSpirit.IdentityApi/RabbitMQProcessingJob.cs new file mode 100644 index 0000000000000000000000000000000000000000..ea03f8b55b5407fcee545912e5621800cbad1e42 --- /dev/null +++ b/Src/CodeSpirit.IdentityApi/RabbitMQProcessingJob.cs @@ -0,0 +1,65 @@ + +using CodeSpirit.Core; +using CodeSpirit.IdentityApi.Services; +using Newtonsoft.Json; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using System.Text; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database; + +namespace CodeSpirit.IdentityApi +{ + public class RabbitMQProcessingJob : BackgroundService + { + private readonly ILogger _logger; + private readonly RabbitMQ.Client.IConnection _connection; + private readonly IServiceProvider _serviceProvider; + private EventingBasicConsumer consumer; + + public RabbitMQProcessingJob(ILogger logger, RabbitMQ.Client.IConnection connection, IServiceProvider serviceProvider) + { + _logger = logger; + _connection = connection; + _serviceProvider = serviceProvider; + } + + + protected override Task ExecuteAsync(CancellationToken stoppingToken) + { + var channel = _connection.CreateModel(); + channel.QueueDeclare(queue: "ExamEvents", durable: false, exclusive: false, autoDelete: false, arguments: null); + consumer = new EventingBasicConsumer(channel); + consumer.Received += ProcessMessageAsync; + channel.BasicConsume(queue: "ExamEvents", autoAck: true, consumer: consumer); + return Task.CompletedTask; + + } + + private async void ProcessMessageAsync(object? sender, BasicDeliverEventArgs args) + { + var body = args.Body.ToArray(); + var message = System.Text.Encoding.UTF8.GetString(body); + + var eventObj = JsonConvert.DeserializeObject(message); + switch (eventObj.EventName) + { + case "StudentDeleted": + // 处理学生删除事件 + var ids = eventObj.EventData.ToObject>(); + _logger.LogInformation("Received StudentDeleted event for Users ID: {userId}", ids); + using (var scope = _serviceProvider.CreateScope()) + { + var userService = scope.ServiceProvider.GetRequiredService(); + await userService.BatchDeleteAsync(ids); + + } + break; + default: + _logger.LogWarning("Unknown event type: {eventName}", eventObj.EventName); + break; + } + + } + + } +}