瀏覽代碼

提交考试、阅卷等功能

guqiang 1 月之前
父節點
當前提交
8b593bd42c
共有 45 個文件被更改,包括 1643 次插入131 次删除
  1. 5 5
      src/Hotline.Api/Controllers/Exam/ExamManageController.cs
  2. 18 1
      src/Hotline.Api/Controllers/Exam/QuestionController.cs
  3. 133 0
      src/Hotline.Api/Controllers/Exam/UserExamController.cs
  4. 31 0
      src/Hotline.Application/Exam/Constants/ApiRoutes/UserExamApiRoute.cs
  5. 2 0
      src/Hotline.Application/Exam/Core/Constants/ApiRoute.cs
  6. 17 0
      src/Hotline.Application/Exam/Core/Extensions/QuestionTypeExtensions.cs
  7. 56 1
      src/Hotline.Application/Exam/Interface/ExamManages/IUserExamService.cs
  8. 38 0
      src/Hotline.Application/Exam/QueryExtensions/ExamManages/ExamQuestionQueryExtensions.cs
  9. 61 0
      src/Hotline.Application/Exam/QueryExtensions/ExamManages/UserExamQueryExtensions.cs
  10. 2 2
      src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionQueryExtesions.cs
  11. 10 1
      src/Hotline.Application/Exam/Service/ExamManages/ExamManageService.cs
  12. 581 5
      src/Hotline.Application/Exam/Service/ExamManages/UserExamService.cs
  13. 12 0
      src/Hotline.Repository.SqlSugar/Exam/Core/Constants/ExamErrorMessage.cs
  14. 7 1
      src/Hotline.Repository.SqlSugar/Exam/Core/Extensions/ValidateResultExtensions.cs
  15. 10 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IUserExamItemOptionRepository.cs
  16. 25 0
      src/Hotline.Repository.SqlSugar/Exam/Repositories/ExamManages/UserExamItemOptionRepository.cs
  17. 1 1
      src/Hotline.Repository.SqlSugar/Exam/Repositories/ExamManages/UserExamRepository.cs
  18. 5 0
      src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/ExamAnswerValidator.cs
  19. 17 4
      src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/ExamManageValidator.cs
  20. 7 8
      src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/ExamTagValidator.cs
  21. 52 0
      src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/UserExamItemOptionsValidator.cs
  22. 6 0
      src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/UserExamItemValidator.cs
  23. 18 1
      src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/UserExamValidator.cs
  24. 31 0
      src/Hotline.Share/Dtos/ExamManages/ExamQuestionDto.cs
  25. 20 0
      src/Hotline.Share/Dtos/ExamManages/ExamQuestionOptionsDto.cs
  26. 22 0
      src/Hotline.Share/Dtos/ExamManages/GradingExamDto.cs
  27. 126 0
      src/Hotline.Share/Dtos/ExamManages/GradingExamQuestionDto.cs
  28. 0 39
      src/Hotline.Share/Dtos/ExamManages/GradingExtamDto.cs
  29. 5 5
      src/Hotline.Share/Dtos/ExamManages/GradingExtamItemDto.cs
  30. 8 1
      src/Hotline.Share/Dtos/ExamManages/SubmitExamDto.cs
  31. 13 30
      src/Hotline.Share/Dtos/ExamManages/UserExamDto.cs
  32. 37 5
      src/Hotline.Share/Dtos/ExamManages/UserExamItemDto.cs
  33. 54 0
      src/Hotline.Share/Dtos/ExamManages/UserExamItemOptionDto.cs
  34. 14 0
      src/Hotline.Share/Requests/Exam/ExamQuestionGroupRequest.cs
  35. 14 0
      src/Hotline.Share/Requests/Exam/ExamQuestionRequest.cs
  36. 21 0
      src/Hotline.Share/Requests/Exam/GradingExamRequest.cs
  37. 59 9
      src/Hotline.Share/Requests/Exam/UserExamPagedRequest.cs
  38. 5 3
      src/Hotline.Share/Requests/Question/QuestionPagedRequest.cs
  39. 14 0
      src/Hotline.Share/Requests/Question/QuestionRequest.cs
  40. 27 0
      src/Hotline.Share/ViewResponses/Exam/ExamQuestionViewResponse.cs
  41. 14 1
      src/Hotline.Share/ViewResponses/Exam/UserExamResultViewResponse.cs
  42. 14 0
      src/Hotline.Share/ViewResponses/SimpleViewResponse.cs
  43. 1 8
      src/Hotline/Exams/ExamManages/ExamAnswer.cs
  44. 28 0
      src/Hotline/Exams/ExamManages/UserExamItemOptions.cs
  45. 2 0
      src/Hotline/Exams/Validate/ErrorMessage.cs

+ 5 - 5
src/Hotline.Api/Controllers/Exam/ExamManageController.cs

@@ -17,7 +17,7 @@ namespace Hotline.Api.Controllers.Exam
         }
 
         /// <summary>
-        /// 新增题库
+        /// 新增考试
         /// </summary>
         /// <param name="questionDto"></param>
         /// <returns></returns>
@@ -28,7 +28,7 @@ namespace Hotline.Api.Controllers.Exam
         }
 
         /// <summary>
-        /// 修改题库
+        /// 修改考试
         /// </summary>
         /// <param name="questionDto"></param>
         /// <returns></returns>
@@ -39,7 +39,7 @@ namespace Hotline.Api.Controllers.Exam
         }
 
         /// <summary>
-        /// 删除题库
+        /// 删除考试
         /// </summary>
         /// <param name="entityQueryRequest"></param>
         /// <returns></returns>
@@ -50,7 +50,7 @@ namespace Hotline.Api.Controllers.Exam
         }
 
         /// <summary>
-        /// 获取题库分页列表
+        /// 获取考试分页列表
         /// </summary>
         /// <param name="questionPagedRequest"></param>
         /// <returns></returns>
@@ -63,7 +63,7 @@ namespace Hotline.Api.Controllers.Exam
         }
 
         /// <summary>
-        /// 获取题库
+        /// 获取考试
         /// </summary>
         /// <param name="id"></param>
         /// <returns></returns>

+ 18 - 1
src/Hotline.Api/Controllers/Exam/QuestionController.cs

@@ -4,6 +4,7 @@ using Exam.Share.ViewResponses.Question;
 using Hotline.Application.Exam.Constants.ApiRoutes;
 using Hotline.Share.Dtos.Questions;
 using Hotline.Share.Requests.Question;
+using MapsterMapper;
 using Microsoft.AspNetCore.Mvc;
 
 namespace Hotline.Api.Controllers.Exam
@@ -11,9 +12,12 @@ namespace Hotline.Api.Controllers.Exam
     public class QuestionController : BaseController
     {
         private readonly IQuestionService _questionService;
-        public QuestionController(IQuestionService questionService)
+        private readonly IMapper _mapper;
+
+        public QuestionController(IQuestionService questionService,IMapper mapper)
         {
             _questionService = questionService;
+            this._mapper = mapper;
         }
 
         /// <summary>
@@ -78,5 +82,18 @@ namespace Hotline.Api.Controllers.Exam
             return questionDto;
         }
 
+        /// <summary>
+        /// 获取题库
+        /// </summary>
+        /// <param name="questionRequest"></param>
+        /// <returns></returns>
+        [HttpPost(QuestionApiRoute.GetList)]
+        public async Task<(int,List<QuestionViewResponse>)> GetList([FromBody]QuestionRequest questionRequest)
+        {
+            var questionPagedRequest = _mapper.Map<QuestionRequest, QuestionPagedRequest>(questionRequest);
+
+            return await _questionService.GetListAsync(questionPagedRequest);
+        }
+
     }
 }

+ 133 - 0
src/Hotline.Api/Controllers/Exam/UserExamController.cs

@@ -0,0 +1,133 @@
+using Exam.Application.Interface.Exam;
+using Exam.Share;
+using Exam.Share.Dtos.ExamManage;
+using Exam.Share.ViewResponses.Exam;
+using Hotline.Application.Exam.Constants.ApiRoutes;
+using Hotline.Share.Dtos.ExamManages;
+using Hotline.Share.Requests.Exam;
+using Hotline.Share.ViewResponses.Exam;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Hotline.Api.Controllers.Exam
+{
+    public class UserExamController : BaseController
+    {
+        private readonly IUserExamService _userExamService;
+
+        public UserExamController(IUserExamService userExamService)
+        {
+            this._userExamService = userExamService;
+        }
+
+        /// <summary>
+        /// 获取我的考试结果
+        /// </summary>
+        /// <param name="userExamPagedRequest"></param>
+        /// <returns></returns>
+        [HttpPost(UserExamApiRoute.GetPagedList)]
+        public async Task<UserExamResultPageViewResponse> GetPagedList([FromBody] UserExamPagedRequest userExamPagedRequest)
+        {
+            return await _userExamService.GetPagedListAsync(userExamPagedRequest) as UserExamResultPageViewResponse;
+        }
+
+        /// <summary>
+        /// 开始考试
+        /// </summary>
+        /// <param name="addUserExamDto"></param>
+        /// <returns></returns>
+        [HttpPost(UserExamApiRoute.Start)]
+        public async Task Start([FromBody] StartUserExamDto startUserExamDto)
+        {
+            await _userExamService.StartUserExamAsync(startUserExamDto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 获取试题类型分组
+        /// </summary>
+        /// <param name="examQuestionGroupRequest"></param>
+        /// <returns></returns>
+        [HttpGet(UserExamApiRoute.GetExamQuestionGroup)]
+        public async Task<List<ExamQuestionViewResponse>> GetExamQuestionGroup([FromQuery]ExamQuestionGroupRequest examQuestionGroupRequest)
+        {
+            return await _userExamService.GetExamQuestionViewResponses(examQuestionGroupRequest);
+        }
+
+        /// <summary>
+        /// 获取试题详情
+        /// </summary>
+        /// <param name="examQuestionRequest"></param>
+        /// <returns></returns>
+        [HttpGet(UserExamApiRoute.GetExamQuestion)]
+        public async Task<ExamQuestionDto> GetExamQuestion([FromQuery]ExamQuestionRequest examQuestionRequest)
+        {
+            return await _userExamService.GetExamQuestionDto(examQuestionRequest);
+        }
+
+        /// <summary>
+        /// 考试
+        /// </summary>
+        /// <param name="addUserExamItemDto"></param>
+        /// <returns></returns>
+        [HttpPost(UserExamApiRoute.Exam)]
+        public async Task<UserExamQuestionDto> Exam([FromBody] AddUserExamItemDto addUserExamItemDto)
+        {
+            return await _userExamService.ExamAsync(addUserExamItemDto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 更新考试
+        /// </summary>
+        /// <param name="updateUserExamItemDto"></param>
+        /// <returns></returns>
+        [HttpPut(UserExamApiRoute.UpdateExam)]
+        public async Task<UserExamQuestionDto> UpdateExam([FromBody]UpdateUserExamItemDto updateUserExamItemDto)
+        {
+           return await _userExamService.UpdateExamAsync(updateUserExamItemDto,HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 提交考试
+        /// </summary>
+        /// <param name="submitExamDto"></param>
+        /// <returns></returns>
+        [HttpPost(UserExamApiRoute.Submit)]
+        public async Task Submit([FromBody] SubmitExamDto submitExamDto)
+        {
+            await _userExamService.SubmitAsync(submitExamDto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 阅卷
+        /// </summary>
+        /// <param name="gradingExtamDto"></param>
+        /// <returns></returns>
+        [HttpPost(UserExamApiRoute.Grading)]
+        public async Task<GradingExamQuestionDto> Grading([FromBody] GradingExtamItemDto gradingExtamItemDto)
+        {
+          return  await _userExamService.GradingAsync(gradingExtamItemDto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 完成阅卷
+        /// </summary>
+        /// <param name="gradingExtamDto"></param>
+        /// <returns></returns>
+        [HttpPost(UserExamApiRoute.CompleteGrading)]
+        public async Task CompleteGrading([FromBody] GradingExamDto gradingExtamDto)
+        {
+            await _userExamService.CompleteGradingAsync(gradingExtamDto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 获取阅卷试题
+        /// </summary>
+        /// <param name="gradingExamRequest"></param>
+        /// <returns></returns>
+        [HttpPost(UserExamApiRoute.GetGradingQuestions)]
+        public async Task<List<GradingExamQuestionDto>> GetGradingQuestions([FromBody] GradingExamRequest gradingExamRequest)
+        {
+            return await _userExamService.GetGradingExamQuestion(gradingExamRequest);
+        }
+    }
+}

+ 31 - 0
src/Hotline.Application/Exam/Constants/ApiRoutes/UserExamApiRoute.cs

@@ -0,0 +1,31 @@
+using Exam.Infrastructure.Web.Constants;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Exam.Constants.ApiRoutes
+{
+
+    public class UserExamApiRoute:ApiRoute
+    {
+        public const string Start = "Start";
+
+        public const string GetExamQuestionGroup = "GetExamQuestionGroup";
+
+        public const string GetExamQuestion = "GetExamQuestion";
+
+        public const string Exam = "Exam";
+
+        public const string UpdateExam = "UpdateExam";
+
+        public const string Submit = "Submit";
+
+        public const string Grading = "Grading";
+
+        public const string CompleteGrading = "CompleteGrading";
+
+        public const string GetGradingQuestions = "GetGradingQuestions";
+    }
+}

+ 2 - 0
src/Hotline.Application/Exam/Core/Constants/ApiRoute.cs

@@ -11,5 +11,7 @@
         public const string GetPagedList = "GetPagedList";
 
         public const string Get = "Get";
+
+        public const string GetList = "GetList";
     }
 }

+ 17 - 0
src/Hotline.Application/Exam/Core/Extensions/QuestionTypeExtensions.cs

@@ -0,0 +1,17 @@
+using Hotline.Share.Enums.Exams;
+
+namespace Hotline.Application.Exam.Core.Extensions
+{
+    public static class QuestionTypeExtensions
+    {
+        /// <summary>
+        /// 是否选择类型
+        /// </summary>
+        /// <param name="questionType"></param>
+        /// <returns></returns>
+        public static bool CheckSelectType(this EQuestionType questionType)
+        {
+            return questionType == EQuestionType.Single || questionType == EQuestionType.Multi || questionType == EQuestionType.Judge;
+        }
+    }
+}

+ 56 - 1
src/Hotline.Application/Exam/Interface/ExamManages/IUserExamService.cs

@@ -4,7 +4,9 @@ using Exam.Share;
 using Exam.Share.Dtos.ExamManage;
 using Exam.Share.ViewResponses.Exam;
 using Hotline.Repository.SqlSugar.Interface;
+using Hotline.Share.Dtos.ExamManages;
 using Hotline.Share.Requests.Exam;
+using Hotline.Share.ViewResponses.Exam;
 
 namespace Exam.Application.Interface.Exam
 {
@@ -17,6 +19,7 @@ namespace Exam.Application.Interface.Exam
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
         Task SubmitAsync(SubmitExamDto submitExamDto,CancellationToken cancellationToken);
+
         
         /// <summary>
         /// 阅卷
@@ -24,6 +27,58 @@ namespace Exam.Application.Interface.Exam
         /// <param name="gradingExtamItemDto"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        Task GradingAsync(GradingExtamItemDto gradingExtamItemDto, CancellationToken cancellationToken);
+        Task<GradingExamQuestionDto> GradingAsync(GradingExtamItemDto gradingExtamItemDto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 获取考试试题类型和Id
+        /// </summary>
+        /// <param name="examQuestionRequest"></param>
+        /// <returns></returns>
+        Task<List<ExamQuestionViewResponse>> GetExamQuestionViewResponses(ExamQuestionGroupRequest examQuestionGroupRequest);
+
+        /// <summary>
+        /// 获取考试试题
+        /// </summary>
+        /// <param name="examQuestionRequest"></param>
+        /// <returns></returns>
+        Task<ExamQuestionDto> GetExamQuestionDto(ExamQuestionRequest examQuestionRequest);
+
+        /// <summary>
+        /// 初次考试
+        /// </summary>
+        /// <param name="addUserExamItemDto"></param>
+        /// <returns></returns>
+        Task<UserExamQuestionDto> ExamAsync(AddUserExamItemDto addUserExamItemDto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 修改考试选项
+        /// </summary>
+        /// <param name="addUserExamItemDto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<UserExamQuestionDto> UpdateExamAsync(UpdateUserExamItemDto updateUserExamItemDto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 开始考试
+        /// </summary>
+        /// <param name="startUserExamDto"></param>
+        /// <param name="CancellationToken"></param>
+        /// <returns></returns>
+        Task StartUserExamAsync(StartUserExamDto startUserExamDto,CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 完成阅卷
+        /// </summary>
+        /// <param name="gradingExtamDto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task CompleteGradingAsync(GradingExamDto gradingExtamDto,CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 获取阅卷试题
+        /// </summary>
+        /// <param name="gradingExamRequest"></param>
+        /// <returns></returns>
+        Task<List<GradingExamQuestionDto>> GetGradingExamQuestion(GradingExamRequest gradingExamRequest);
     }
 }

+ 38 - 0
src/Hotline.Application/Exam/QueryExtensions/ExamManages/ExamQuestionQueryExtensions.cs

@@ -0,0 +1,38 @@
+using Exam.ExamManages;
+using Exam.Infrastructure.Extensions;
+using Exam.Infrastructure.Web.Utilities;
+using Exam.Questions;
+using Hotline.Share.Requests.Exam;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Exam.QueryExtensions.ExamManages
+{
+    public static class ExamQuestionQueryExtensions
+    {
+
+        public static Expression<Func<ExamManage,bool>> GetExpression(this ExamQuestionGroupRequest examQuestionGroupRequest)
+        {
+            Expression<Func<ExamManage, bool>> expression = m => m.Id != null;
+
+            expression = ExpressionableUtility.CreateExpression<ExamManage>()
+                .AndIF(examQuestionGroupRequest.ExamId.IsNotNullOrEmpty(), x => x.Id == examQuestionGroupRequest.ExamId).ToExpression();
+
+            return expression;
+        }
+
+        public static Expression<Func<Question,bool>> GetExpression(this ExamQuestionRequest examQuestionRequest)
+        {
+            Expression<Func<Question, bool>> expression = m => m.Id != null;
+
+            expression = ExpressionableUtility.CreateExpression<Question>()
+                .AndIF(examQuestionRequest.QuestionId.IsNotNullOrEmpty(), x => x.Id == examQuestionRequest.QuestionId).ToExpression();
+
+            return expression;
+        }
+    }
+}

+ 61 - 0
src/Hotline.Application/Exam/QueryExtensions/ExamManages/UserExamQueryExtensions.cs

@@ -0,0 +1,61 @@
+using Exam.ExamManages;
+using Exam.Infrastructure.Extensions;
+using Exam.Infrastructure.Web.Utilities;
+using Exam.Share;
+using Hotline.Share.Requests.Exam;
+using JiebaNet.Segmenter.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Exam.QueryExtensions.ExamManages
+{
+    public static class UserExamQueryExtensions
+    {
+        public static Expression<Func<UserExam,bool>> GetExpression(this UserExamPagedRequest userExamPagedRequest)
+        {
+            Expression<Func<UserExam, bool>> expression = m => m.Id != null;
+
+            expression = ExpressionableUtility.CreateExpression<UserExam>()
+                .AndIF(userExamPagedRequest.MinScore.IsNotNull(), x => x.Score >= userExamPagedRequest.MinScore)
+                .AndIF(userExamPagedRequest.MaxScore.IsNotNull(), x => x.Score <= userExamPagedRequest.MaxScore)
+                .And(x=>x.UserId == userExamPagedRequest.UserId)
+                .ToExpression();
+
+            return expression;
+        }
+
+        public static Expression<Func<ExamManage,bool>> GetExamManageExpression(this UserExamPagedRequest userExamPagedRequest)
+        {
+            Expression<Func<ExamManage, bool>> expression = m => m.Id != null;
+
+            expression = ExpressionableUtility.CreateExpression<ExamManage>()
+                .AndIF(userExamPagedRequest.Name.IsNotNullOrEmpty(), x => x.Name.Contains(userExamPagedRequest.Name))
+                .AndIF(userExamPagedRequest.ExamType.IsNotNull(), x => x.ExamType == userExamPagedRequest.ExamType)
+                .AndIF(userExamPagedRequest.MinStartTime.IsNotNull(),x=>x.StartTime >= userExamPagedRequest.MinStartTime)
+                .AndIF(userExamPagedRequest.MaxStartTime.IsNotNull(),x=>x.StartTime <= userExamPagedRequest.MaxStartTime)
+                .AndIF(userExamPagedRequest.MinEndTime.IsNotNull(), x => x.EndTime >= userExamPagedRequest.MinEndTime)
+                .AndIF(userExamPagedRequest.MaxEndTime.IsNotNull(),x=>x.EndTime <= userExamPagedRequest.MaxEndTime)
+                .AndIF(userExamPagedRequest.MinTotalScore.IsNotNull(),x=>x.TotalScore >= userExamPagedRequest.MinTotalScore)
+                .AndIF(userExamPagedRequest.MaxTotalScore.IsNotNull(),x=>x.TotalScore <= userExamPagedRequest.MaxTotalScore)
+                .ToExpression();
+
+            return expression;
+        }
+
+    
+        public static Expression<Func<UserExam,bool>> GetExpression(this GradingExamRequest gradingExamRequest)
+        {
+            Expression<Func<UserExam, bool>> expression = m => m.Id != null;
+
+            expression = ExpressionableUtility.CreateExpression<UserExam>()
+                .AndIF(gradingExamRequest.ExamId.IsNotNullOrEmpty(), x => x.ExamId == gradingExamRequest.ExamId)
+                .AndIF(gradingExamRequest.UserId.IsNotNullOrEmpty(), x => x.UserId == gradingExamRequest.UserId).ToExpression();
+
+            return expression;
+        }
+    }
+}

+ 2 - 2
src/Hotline.Application/Exam/QueryExtensions/Questions/QuestionQueryExtesions.cs

@@ -25,7 +25,7 @@ namespace Hotline.Application.Exam.QueryExtensions.Questions
         {
             Expression<Func<QuestionTag, bool>> expression = m => m.Id != null;
 
-            expression = ExpressionableUtility.CreateExpression<QuestionTag>().AndIF(questionPagedRequest.TagId.IsNotNullOrEmpty(), x => questionPagedRequest.TagId == x.TagId)
+            expression = ExpressionableUtility.CreateExpression<QuestionTag>().AndIF(questionPagedRequest.TagIds.IsNotNull(), x => questionPagedRequest.TagIds.Contains(x.TagId))
             .ToExpression();
             return expression;
         }
@@ -35,7 +35,7 @@ namespace Hotline.Application.Exam.QueryExtensions.Questions
             Expression<Func<ExamTag, bool>> expression = m => m.Id != null;
 
             expression = ExpressionableUtility.CreateExpression<ExamTag>().
-            AndIF(questionPagedRequest.TagId.IsNotNullOrEmpty(), x => questionPagedRequest.TagId == x.Id)
+            AndIF(questionPagedRequest.TagIds.IsNotNull(), x => questionPagedRequest.TagIds.Contains(x.Id))
             .ToExpression();
 
             return expression;

+ 10 - 1
src/Hotline.Application/Exam/Service/ExamManages/ExamManageService.cs

@@ -23,6 +23,9 @@ using XF.Domain.Dependency;
 using XF.Domain.Entities;
 using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Application.Exam.QueryExtensions.ExamManages;
+using XF.Domain.Exceptions;
+using NetTaste;
+using Exam.Infrastructure.Validation.Validation;
 
 namespace Hotline.Application.Exam.Service.ExamManages
 {
@@ -221,6 +224,8 @@ namespace Hotline.Application.Exam.Service.ExamManages
                 userExams.Add(_mapper.Map<UserExam>(x));
             });
 
+            userExams.ToInsert();
+
             await _userExamRepository.AddWithValidateAsync(userExams, cancellationToken);
 
         }
@@ -337,7 +342,11 @@ namespace Hotline.Application.Exam.Service.ExamManages
         {
             var examQuestionScoreDtos = actionRequest.ExamQuestionScoreDtos.Where(x => x.OperationStatus != EEOperationStatus.Delete);
 
-            actionRequest.TotalScore = examQuestionScoreDtos.Sum(x => x.Count * x.Score);
+            var totalScore = examQuestionScoreDtos.Sum(x => x.Count * x.Score);
+            if(totalScore != actionRequest.TotalScore)
+            {
+                throw new UserFriendlyException(ErrorMessage.ServiceError,string.Format(ErrorMessage.IsNotEqual, "试题分数总和",typeof(AddExamManageDto).GetDescription(nameof(AddExamManageDto.TotalScore))));
+            }
         }
         #endregion
     }

+ 581 - 5
src/Hotline.Application/Exam/Service/ExamManages/UserExamService.cs

@@ -1,10 +1,586 @@
-using System;
-using System.Collections.Generic;
+using DocumentFormat.OpenXml.Drawing;
+using DocumentFormat.OpenXml.Drawing.Charts;
+using DocumentFormat.OpenXml.Office2010.Excel;
+using Exam.Application.Interface.Exam;
+using Exam.ExamManages;
+using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Data.Interface;
+using Exam.Infrastructure.Enums;
+using Exam.Infrastructure.Validation.Validation;
+using Exam.Infrastructure.Web.Utilities;
+using Exam.Insfrastructure.Service.Service;
+using Exam.Questions;
+using Exam.Repository.Sqlsugar.Repositories;
+using Exam.Share;
+using Exam.Share.Dtos.ExamManage;
+using Exam.Share.ViewResponses.Exam;
+using Exam.TestPapers;
+using Hotline.Application.Exam.Core.Extensions;
+using Hotline.Application.Exam.QueryExtensions.ExamManages;
+using Hotline.Exams.ExamManages;
+using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Repository.SqlSugar.Interface;
+using Hotline.Share.Dtos.ExamManages;
+using Hotline.Share.Requests.Exam;
+using Hotline.Share.ViewResponses;
+using Hotline.Share.ViewResponses.Exam;
+using JiebaNet.Segmenter.Common;
+using MapsterMapper;
+using SqlSugar;
 using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
 
 namespace Hotline.Application.Exam.Service.ExamManages
 {
-   
+    public class UserExamService : ApiService<UserExam, AddUserExamDto, UpdateUserExamDto, HotlineDbContext>, IUserExamService, IScopeDependency
+    {
+        private readonly IUserExamRepository _repository;
+        private readonly IUserExamItemRepository _userExamItemRepository;
+        private readonly IUserExamItemOptionRepository _userExamItemOptionRepository;
+        private readonly IDataPermissionFilterBuilder _dataPermissionFilterBuilder;
+        private readonly IServiceProvider _serviceProvider;
+        private readonly IMapper _mapper;
+        private readonly ISessionContext _sessionContext
+            ;
+
+        public UserExamService(IUserExamRepository repository,
+            IUserExamItemRepository userExamItemRepository,
+            IUserExamItemOptionRepository userExamItemOptionRepository,
+            IDataPermissionFilterBuilder dataPermissionFilterBuilder, IServiceProvider serviceProvider,
+            IMapper mapper, ISessionContext sessionContext) : base(repository, mapper)
+        {
+            this._repository = repository;
+            this._userExamItemRepository = userExamItemRepository;
+            this._userExamItemOptionRepository = userExamItemOptionRepository;
+            this._dataPermissionFilterBuilder = dataPermissionFilterBuilder;
+            this._serviceProvider = serviceProvider;
+            this._mapper = mapper;
+            this._sessionContext = sessionContext;
+        }
+
+        #region public method
+        public Task<UserExamDto> GetAsync(EntityQueryRequest entityQueryRequest)
+        {
+            throw new NotImplementedException();
+        }
+        public Task<(int, List<UserExamResultViewResponse>)> GetListAsync(UserExamPagedRequest queryRequest)
+        {
+            throw new NotImplementedException();
+        }
+
+        public async Task<ExamQuestionDto> GetExamQuestionDto(ExamQuestionRequest examQuestionRequest)
+        {
+            var expression = examQuestionRequest.GetExpression();
+            var quesetion = await new ExamRepository<Question>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(expression).FirstAsync();
+
+            if (quesetion != null)
+            {
+                var examQuestionDto = _mapper.Map<ExamQuestionDto>(quesetion);
+
+                if (examQuestionDto.QuestionType == Share.Enums.Exams.EQuestionType.Multi
+                    || examQuestionDto.QuestionType == Share.Enums.Exams.EQuestionType.Single
+                    || examQuestionDto.QuestionType == Share.Enums.Exams.EQuestionType.Judge
+                    )
+                {
+                    var questionOptions = await new ExamRepository<QuestionOptions>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(x => x.QuestionId == quesetion.Id).ToListAsync();
+
+                    if (questionOptions != null)
+                    {
+                        examQuestionDto.QuestionOptions = new List<ExamQuestionOptionsDto>();
+
+                        questionOptions.ForEach(item =>
+                        {
+                            examQuestionDto.QuestionOptions.Add(_mapper.Map<ExamQuestionOptionsDto>(item));
+                        });
+                    }
+
+                }
+
+
+                return examQuestionDto;
+            }
+            else
+            {
+                throw new UserFriendlyException(ErrorMessage.ServiceError, string.Format(ErrorMessage.IsNotExists, nameof(Question)));
+            }
+
+        }
+
+        public async Task<List<ExamQuestionViewResponse>> GetExamQuestionViewResponses(ExamQuestionGroupRequest examQuestionGroupRequest)
+        {
+            var expression = examQuestionGroupRequest.GetExpression();
+            var examManageTable = new ExamRepository<ExamManage>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(expression);
+            var testPaperItemTable = new ExamRepository<TestPaperItem>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+            var questionTable = new ExamRepository<Question>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+
+            var queryable = await examManageTable.InnerJoin(testPaperItemTable, (e, i) => e.TestPaperId == i.TestPaperId)
+                .InnerJoin(questionTable, (e, i, q) => i.QuestionId == q.Id)
+                .Select((e, i, q) => q).ToListAsync();
+
+            var result = queryable.GroupBy(x => x.QuestionType).Select(m => new ExamQuestionViewResponse
+            {
+                QuestionType = m.Key,
+                Questions = m.Select(n => new SimpleViewResponse
+                {
+                    Id = n.Id
+                }).ToList<IViewResponse>()
+            }).ToList();
+
+            return result;
+
+        }
+
+
+        public async Task<PageViewResponse<UserExamResultViewResponse>> GetPagedListAsync(UserExamPagedRequest queryRequest)
+        {
+            SqlSugar.ISugarQueryable<UserExamResultViewResponse> queryable = GetQueryable(queryRequest);
+
+            var list = await queryable.ToPageListAsync(queryRequest.PageIndex, queryRequest.PageSize);
+            var total = await queryable.CountAsync();
+
+            var result = new UserExamResultPageViewResponse
+            {
+                Items = list,
+                Pagination = new Pagination(queryRequest.PageIndex, queryRequest.PageSize, total)
+            };
+
+            return result;
+        }
+        public async Task<GradingExamQuestionDto> GradingAsync(GradingExtamItemDto gradingExtamItemDto, CancellationToken cancellationToken)
+        {
+            var userExamItem = await _userExamItemRepository.GetAsync(m => m.Id == gradingExtamItemDto.UserExamItemId);
+
+            if (userExamItem != null)
+            {
+                userExamItem = _mapper.Map<GradingExtamItemDto, UserExamItem>(gradingExtamItemDto, userExamItem);
+
+                await _userExamItemRepository.UpdateWithValidateAsync(userExamItem, cancellationToken);
+            }
+
+            return await GetNextExamQuestion(gradingExtamItemDto);
+        }
+
+        public async Task SubmitAsync(SubmitExamDto submitExamDto, CancellationToken cancellationToken)
+        {
+            var userExam = await _repository.GetAsync(x => x.Id == submitExamDto.Id);
+
+            if (userExam != null)
+            {
+                userExam = _mapper.Map<SubmitExamDto, UserExam>(submitExamDto, userExam);
+
+                await _repository.UpdateWithValidateAsync(userExam, cancellationToken);
+            }
+        }
+
+        public async Task<UserExamQuestionDto> ExamAsync(AddUserExamItemDto addUserExamItemDto, CancellationToken cancellationToken)
+        {
+            await AddUserExamItem(addUserExamItemDto, cancellationToken);
+
+            await AddUserExamItemOptions(addUserExamItemDto, cancellationToken);
+
+            await AddExamAnswer(addUserExamItemDto,cancellationToken);
+
+            var gradingExamQuestionDto = await GetNextExamQuestion(addUserExamItemDto);
+
+            return gradingExamQuestionDto;
+        }
+
+
+        public async Task<UserExamQuestionDto> UpdateExamAsync(UpdateUserExamItemDto updateUserExamItemDto, CancellationToken cancellationToken)
+        {
+            await UpdateUserExamItem(updateUserExamItemDto, cancellationToken);
+
+            await ModifyUserItemOptions(updateUserExamItemDto, cancellationToken);
+
+            await UpdateExamAnswer(updateUserExamItemDto,cancellationToken);
+
+            var gradingExamQuestionDto = await GetNextExamQuestion(updateUserExamItemDto);
+
+            return gradingExamQuestionDto;
+        }
+        public async Task StartUserExamAsync(StartUserExamDto startUserExamDto, CancellationToken cancellationToken)
+        {
+            var userExam = await _repository.GetAsync(x => x.Id == startUserExamDto.Id);
+
+            if (userExam == null) return;
+
+            userExam.ExamStatus = Share.Enums.Exams.EExamStatus.Executing;
+
+            userExam.ToUpdate();
+
+            await _repository.UpdateWithValidateAsync(userExam, cancellationToken);
+        }
+
+        public async Task CompleteGradingAsync(GradingExamDto gradingExtamDto, CancellationToken cancellationToken)
+        {
+            var userExam = await _repository.GetAsync(x => x.Id == gradingExtamDto.Id);
+
+            if (userExam == null) return;
+
+            var userExamItems = await _userExamItemRepository.Queryable().Where(x => x.UserExamId == gradingExtamDto.Id).ToListAsync();
+
+            if (userExamItems != null)
+            {
+                var totalScore = userExamItems.Sum(x => x.Score);
+                userExam.Score = totalScore;
+
+                var examManage = await new ExamRepository<ExamManage>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).GetAsync(x => x.Id == userExam.ExamId);
+                userExam.IsSuccess = totalScore >= examManage.CutoffScore;
+
+                userExam.ExamStatus = Share.Enums.Exams.EExamStatus.Complete;
+
+                userExam.ToUpdate();
+
+                await _repository.UpdateWithValidateAsync(userExam,cancellationToken);
+            }
+        }
+
+        public async Task<List<GradingExamQuestionDto>> GetGradingExamQuestion(GradingExamRequest gradingExamRequest)
+        {
+            var expression = gradingExamRequest.GetExpression();
+            var userExamTable =  _repository.Queryable().Where(expression);
+
+            var userExamItemTable = _userExamItemRepository.Queryable();
+            var userExamItemOptionTable = _userExamItemOptionRepository.Queryable();
+            var examAnswerTable = new ExamRepository<ExamAnswer>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+            var questionTable = new ExamRepository<Question>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+            var quesitonOptionTable = new ExamRepository<QuestionOptions>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+
+            var queryResult = await userExamTable.InnerJoin(userExamItemTable, (e, i) => e.Id == i.UserExamId)
+                .InnerJoin(questionTable, (e, i, q) => i.QuestionId == q.Id)
+                .LeftJoin(userExamItemOptionTable, (e, i, q, o) => i.Id == o.UserExamItemId)
+                .LeftJoin(quesitonOptionTable, (e, i, q, o, qo) => o.QuestionOptionId == qo.Id)
+                .LeftJoin(examAnswerTable, (e, i, q, o, qo, a) => i.UserExamId == a.UserExamItemId)
+                .Select(
+                (e, i, q, o, qo, a) => new GradingExamQuestionTempDto
+                {
+                    Id = i.Id,
+                    QuestionType = q.QuestionType,
+                    Answer = a != null ? a.Answer : string.Empty,
+                    Title = q.Title,
+                    QuestionOptionId = o.Id,
+                    UserExamItemId = i.Id,
+                    Content = qo.Content,
+                    IsAnswer = qo.IsAnswer
+                }
+                ).ToListAsync();
+
+            var gradingExamQuestionDtos = queryResult.GroupBy(x => new
+            {
+                Id = x.Id,
+                QuestionType = x.QuestionType
+            }).Select(g => new GradingExamQuestionDto
+            {
+                Answer = g.FirstOrDefault().Answer,
+                QuestionType = g.Key.QuestionType,
+                Id = g.Key.Id,
+                Title = g.FirstOrDefault().Title,
+                UserExamItemOptionDtos = g.Select(i=>new GradingUserExamItemOptionDto
+                {
+                    QuestionOptionId =i.QuestionOptionId,
+                    UserExamItemId =i.UserExamItemId,
+                    Content = i.Content,
+                    IsAnswer = i.IsAnswer
+
+                }).ToList()
+            }).ToList();
+
+            return gradingExamQuestionDtos;
+        }
+        #endregion
+
+        #region private method
+
+
+        private async Task<GradingExamQuestionDto> GetNextExamQuestion(GradingExtamItemDto gradingExtamItemDto)
+        {
+            // TODO: 获取未阅卷的第一道题
+            var current = _userExamItemRepository.Queryable().Where(x => x.Id == gradingExtamItemDto.UserExamItemId);
+            var userExamItemTable = _userExamItemRepository.Queryable().Where(x => !x.IsCheck);
+
+            var userExamItem = current.InnerJoin(userExamItemTable, (c, u) => c.UserExamId == u.UserExamId).OrderBy(x => x.SortIndex).First();
+
+            if (userExamItem != null)
+            {
+                var question = new ExamRepository<Question>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(x => x.Id == userExamItem.Id).First();
+
+                if (question == null) return null;
+
+                var gradingExamQuestionDto = new GradingExamQuestionDto();
+
+                gradingExamQuestionDto = _mapper.Map<Question, GradingExamQuestionDto>(question, gradingExamQuestionDto);
+
+                if (question.QuestionType.CheckSelectType())
+                {
+                    var userExamItemOptionTable = _userExamItemOptionRepository.Queryable().Where(x => x.UserExamItemId == userExamItem.Id);
+                    var quesitonOptionTable = new ExamRepository<QuestionOptions>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+
+                    var queryResult = userExamItemOptionTable.InnerJoin(quesitonOptionTable, (u, q) => u.QuestionOptionId == q.Id)
+                        .Select((u, q) => new GradingUserExamItemOptionDto
+                        {
+                            Content = q.Content,
+                            QuestionOptionId = u.QuestionOptionId,
+                            UserExamItemId = u.UserExamItemId,
+                            IsAnswer = q.IsAnswer
+                        });
+
+                    gradingExamQuestionDto.UserExamItemOptionDtos = queryResult.ToList();
+                }
+                else
+                {
+                    var examAnswer = new ExamRepository<ExamAnswer>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(x => x.UserExamItemId == userExamItem.Id).First();
+
+                    gradingExamQuestionDto.Answer = examAnswer.Answer ?? string.Empty;
+                }
+
+                return gradingExamQuestionDto;
+
+            }
+
+            return null;
+        }
+
+        private async Task AddExamAnswer(AddUserExamItemDto addUserExamItemDto, CancellationToken cancellationToken)
+        {
+            if (addUserExamItemDto.QuestionType.CheckSelectType()) return;
+
+            var examAnswer = new ExamAnswer
+            {
+                UserId = _sessionContext.UserId,
+                Answer = addUserExamItemDto.Answer
+            };
+
+            examAnswer.ToInsert();
+
+            await new ExamRepository<ExamAnswer>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).AddWithValidateAsync(examAnswer, cancellationToken);
+        }
+
+
+        private async Task<UserExamQuestionDto> GetNextExamQuestion(AddUserExamItemDto addUserExamItemDto)
+        {
+            // TODO: 获取未阅卷的第一道题
+            var userExamItem = _userExamItemRepository.Queryable().Where(x => x.UserExamId == addUserExamItemDto.UserExamId && !x.IsCheck).OrderBy(o => o.SortIndex).First();
+ 
+
+            if (userExamItem != null)
+            {
+                var question = new ExamRepository<Question>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(x => x.Id == userExamItem.Id).First();
+
+                if (question == null) return null;
+
+                var userExamQuestionDto = new UserExamQuestionDto();
+
+                userExamQuestionDto = _mapper.Map<Question, UserExamQuestionDto>(question, userExamQuestionDto);
+
+                if (question.QuestionType.CheckSelectType())
+                {
+                    var userExamItemOptionTable = _userExamItemOptionRepository.Queryable().Where(x => x.UserExamItemId == userExamItem.Id);
+                    var quesitonOptionTable = new ExamRepository<QuestionOptions>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+
+                    var queryResult = userExamItemOptionTable.InnerJoin(quesitonOptionTable, (u, q) => u.QuestionOptionId == q.Id)
+                        .Select((u, q) => new UserExamItemOptionDto
+                        {
+                            Content = q.Content,
+                            QuestionOptionId = u.QuestionOptionId,
+                            UserExamItemId = u.UserExamItemId
+                        });
+
+                    userExamQuestionDto.UserExamItemOptionDtos = queryResult.ToList();
+                }
+                else
+                {
+                    var examAnswer = new ExamRepository<ExamAnswer>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(x => x.UserExamItemId == userExamItem.Id).First();
+
+                    userExamQuestionDto.Answer = examAnswer.Answer ?? string.Empty;
+                }
+
+                return userExamQuestionDto;
+            }
+            else
+            {
+                return null;
+            }
+
+           
+        }
+
+        private async Task UpdateExamAnswer(UpdateUserExamItemDto updateUserExamItemDto, CancellationToken cancellationToken)
+        {
+            var repository = new ExamRepository<ExamAnswer>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider);
+
+            var examAnswerTable = repository.Queryable();
+
+            var userExamItemTable = _userExamItemRepository.Queryable().Where(x => x.Id == updateUserExamItemDto.Id);
+
+            var examAnswer = await examAnswerTable.InnerJoin(userExamItemTable, (e, u) => e.UserExamItemId == u.Id).Select((e, u) => e).FirstAsync();
+
+            if (!updateUserExamItemDto.QuestionType.CheckSelectType())
+            {
+                examAnswer.Answer = updateUserExamItemDto.Answer;
+
+                examAnswer.ToUpdate();
+
+                await repository.AddWithValidateAsync(examAnswer, cancellationToken);
+            }
+        }
+
+
+        private async Task ModifyUserItemOptions(UpdateUserExamItemDto updateUserExamItemDto, CancellationToken cancellationToken)
+        {
+            await UpdateUserItemOptions(updateUserExamItemDto, cancellationToken);
+
+            var addUserExamItemDto = _mapper.Map<AddUserExamItemDto>(updateUserExamItemDto);
+
+            addUserExamItemDto.UserExamItemOptionDtos = new List<AddUserExamItemOptionDto>();
+
+            updateUserExamItemDto.UserExamItemOptionDtos.ForEach(item =>
+            {
+                addUserExamItemDto.UserExamItemOptionDtos.Add(_mapper.Map<AddUserExamItemOptionDto>(item));
+            });
+
+            await AddUserExamItemOptions(addUserExamItemDto, cancellationToken);
+
+            if (updateUserExamItemDto.QuestionType.CheckSelectType())
+            {
+                var ids = updateUserExamItemDto.UserExamItemOptionDtos.Where(x => x.OperationStatus == EEOperationStatus.Delete).Select(x => x.Id);
+                var entityQuestionRequest = new EntityQueryRequest
+                {
+                    Expression = ExpressionableUtility.CreateExpression<UserExamItemOptions>()
+              .AndIF(updateUserExamItemDto.Id.IsNotEmpty(), x => ids.Contains(x.Id)).ToExpression()
+                };
+
+                await DeleteUserExamItemOptions(entityQuestionRequest, cancellationToken);
+            }
+
+        }
+
+        private async Task UpdateUserItemOptions(UpdateUserExamItemDto updateUserExamItemDto, CancellationToken cancellationToken)
+        {
+
+            if (updateUserExamItemDto.QuestionType.CheckSelectType())
+            {
+
+                var userExamItemOptions = await _userExamItemOptionRepository.Queryable().Where(x => x.UserExamItemId == updateUserExamItemDto.Id).ToListAsync();
+
+                var entities = new List<UserExamItemOptions>();
+
+                if (updateUserExamItemDto.UserExamItemOptionDtos != null)
+                {
+                    updateUserExamItemDto.UserExamItemOptionDtos.Where(m => m.OperationStatus == EEOperationStatus.Update).ToList().ForEach(x =>
+                    {
+                        var entity = userExamItemOptions.FirstOrDefault(m => m.Id == x.Id);
+                        if (entity != null)
+                        {
+                            entities.Add(_mapper.Map<UpdateUserExamItemOptionDto, UserExamItemOptions>(x, entity));
+                        }
+
+                    });
+                }
+
+                entities.ToUpdate();
+
+                await _userExamItemOptionRepository.UpdateWithValidateAsync(entities, cancellationToken);
+            }
+
+
+        }
+
+        private async Task UpdateUserExamItem(UpdateUserExamItemDto updateUserExamItemDto, CancellationToken cancellationToken)
+        {
+            var userExamItem = await _userExamItemRepository.GetAsync(x => x.Id == updateUserExamItemDto.Id);
+
+            userExamItem = _mapper.Map<UpdateUserExamItemDto, UserExamItem>(updateUserExamItemDto, userExamItem);
+
+            userExamItem.ToUpdate();
+
+            await _userExamItemRepository.UpdateWithValidateAsync(userExamItem, cancellationToken);
+
+            if (updateUserExamItemDto.QuestionType.CheckSelectType())
+            {
+                if (updateUserExamItemDto.UserExamItemOptionDtos != null)
+                {
+                    updateUserExamItemDto.UserExamItemOptionDtos.ForEach(x => x.UserExamItemId = updateUserExamItemDto.Id);
+                }
+            }
+        }
+
+
+        private async Task AddUserExamItemOptions(AddUserExamItemDto addUserExamItemDto, CancellationToken cancellationToken)
+        {
+            var userExamItemOptions = new List<UserExamItemOptions>();
+
+            if (addUserExamItemDto.QuestionType.CheckSelectType())
+            {
+                if (addUserExamItemDto.UserExamItemOptionDtos != null)
+                {
+                    addUserExamItemDto.UserExamItemOptionDtos.Where(x => x.OperationStatus == EEOperationStatus.Add).ToList().ForEach(x =>
+                    {
+                        userExamItemOptions.Add(_mapper.Map<UserExamItemOptions>(x));
+                    });
+                }
+            }
+
+            userExamItemOptions.ToInsert();
+
+            await _userExamItemOptionRepository.AddWithValidateAsync(userExamItemOptions, cancellationToken);
+        }
+
+        private async Task AddUserExamItem(AddUserExamItemDto addUserExamItemDto, CancellationToken cancellationToken)
+        {
+            var userExamItem = _mapper.Map<UserExamItem>(addUserExamItemDto);
+
+            userExamItem.ToInsert();
+
+            var id = await _userExamItemRepository.AddWithValidateAsync(userExamItem, cancellationToken);
+
+            if (addUserExamItemDto.QuestionType.CheckSelectType())
+            {
+                if (addUserExamItemDto.UserExamItemOptionDtos != null)
+                {
+                    addUserExamItemDto.UserExamItemOptionDtos.ForEach(x => x.UserExamItemId = id);
+                }
+            }
+        }
+
+        private async Task DeleteUserExamItemOptions(EntityQueryRequest entityQueryRequest, CancellationToken cancellationToken)
+        {
+            await _userExamItemOptionRepository.DeleteWithValidateAsync(entityQueryRequest, cancellationToken);
+        }
+
+        private SqlSugar.ISugarQueryable<UserExamResultViewResponse> GetQueryable(UserExamPagedRequest queryRequest)
+        {
+            if (_sessionContext.UserId != null)
+            {
+                queryRequest.UserId = _sessionContext.UserId;
+            }
+            var expression = queryRequest.GetExpression();
+            var userExamTable = _repository.Queryable().Where(expression);
+
+            var examManageExpression = queryRequest.GetExamManageExpression();
+            var examManageTable = new ExamRepository<ExamManage>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(examManageExpression);
+
+            var queryable = userExamTable.InnerJoin(examManageTable, (u, e) => u.ExamId == e.Id).Select((u, e) => new UserExamResultViewResponse
+            {
+                Id = u.Id,
+                CutoffScore = e.CutoffScore,
+                TotalScore = e.TotalScore,
+                ExamName = e.Name,
+                Score = u.Score ?? 0,
+                Status = u.Status,
+                SortIndex = u.SortIndex,
+                ExamStatus = u.ExamStatus,
+                IsSuccess = u.IsSuccess
+            });
+            return queryable;
+        }
+
+
+        #endregion
+
+    }
 }

+ 12 - 0
src/Hotline.Repository.SqlSugar/Exam/Core/Constants/ExamErrorMessage.cs

@@ -0,0 +1,12 @@
+using Exam.Infrastructure.Validation.Validation;
+
+namespace Hotline.Repository.SqlSugar.Exam.Core.Constants
+{
+    public class ExamErrorMessage:ErrorMessage
+    {
+        public const string DeleteUnableUserExam = "考试已经开始,不能删除";
+        
+        public const string DeleteUnableExam = "考试已经开始,不能删除";
+
+    }
+}

+ 7 - 1
src/Hotline.Repository.SqlSugar/Exam/Core/Extensions/ValidateResultExtensions.cs

@@ -45,7 +45,13 @@ namespace Exam.Infrastructure.Validation.Extensions
             var validationError = new ValidationErrors();
             validateKey = validateKey ?? string.Empty;
 
-            foreach (var failure in validationResult.Errors)
+            var errors = validationResult.Errors.GroupBy(x => new
+            {
+                propertyName = x.PropertyName,
+                ErrorMessage = x.ErrorMessage
+            }).Select(m => m.FirstOrDefault());
+
+            foreach (var failure in errors)
             {
                 validationError.AddError(failure.PropertyName, failure.AttemptedValue, failure.ErrorMessage, false, validateKey.ToString());
             }

+ 10 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IUserExamItemOptionRepository.cs

@@ -0,0 +1,10 @@
+using Hotline.Exams.ExamManages;
+using Hotline.Repository.SqlSugar.Interface;
+using XF.Domain.Repository;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages
+{
+    public interface IUserExamItemOptionRepository:IRepository<UserExamItemOptions>,IExamRepository<UserExamItemOptions, HotlineDbContext>
+    {
+    }
+}

+ 25 - 0
src/Hotline.Repository.SqlSugar/Exam/Repositories/ExamManages/UserExamItemOptionRepository.cs

@@ -0,0 +1,25 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Repository.Sqlsugar.Repositories;
+using FluentValidation;
+using Hotline.Exams.ExamManages;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages;
+using Hotline.Repository.SqlSugar.Exam.Validators.ExamManages;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.Exam.Repositories.ExamManages
+{
+    public class UserExamItemOptionRepository : ExamRepository<UserExamItemOptions>, IUserExamItemOptionRepository, IScopeDependency
+    {
+        public UserExamItemOptionRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder, IServiceProvider serviceProvider) : base(uow, dataPermissionFilterBuilder, serviceProvider)
+        {
+            Validator = new UserExamItemOptionsValidator();
+        }
+    }
+}

+ 1 - 1
src/Hotline.Repository.SqlSugar/Exam/Repositories/ExamManages/UserExamRepository.cs

@@ -17,7 +17,7 @@ namespace Exam.Repository.Sqlsugar.Repositories.ExamManages
     {
         public UserExamRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder, IServiceProvider serviceProvider) : base(uow, dataPermissionFilterBuilder, serviceProvider)
         {
-            Validator = new UserExamValidator();
+            Validator = new UserExamValidator(this);
         }
     }
 }

+ 5 - 0
src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/ExamAnswerValidator.cs

@@ -2,12 +2,17 @@ using Exam.ExamManages;
 using Exam.Infrastructure.Extensions;
 using Exam.Infrastructure.Validation.Validation;
 using FluentValidation;
+using Hotline.Repository.SqlSugar.Exam.Core.Constants;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages;
+using Hotline.Repository.SqlSugar.Interface;
 using Hotline.Repository.SqlSugar.Validate;
 
 namespace Hotline.Repository.SqlSugar.Exam.Validators.ExamManages
 {
     public class ExamAnswerValidator:BaseValidator<ExamAnswer>
     {
+        private readonly IExamManageRepository _examManageRepository;
+
         public ExamAnswerValidator()
         {
             RuleSet(ValidatorTypeConstants.Create, () =>

+ 17 - 4
src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/ExamManageValidator.cs

@@ -4,8 +4,10 @@ using Exam.Infrastructure.Extensions;
 using Exam.Infrastructure.Validation.Validation;
 using Exam.TestPapers;
 using FluentValidation;
+using Hotline.Repository.SqlSugar.Exam.Core.Constants;
 using Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages;
 using Hotline.Repository.SqlSugar.Validate;
+using Hotline.Share.Enums.Exams;
 
 namespace Exam.Application
 {
@@ -28,6 +30,12 @@ namespace Exam.Application
 
                 ValidateRuleWithModify();
             });
+
+            RuleSet(ValidatorTypeConstants.Remove, () =>
+            {
+                ValidateRuleWithRemove();
+            });
+
             this._examManageRepository = examManageRepository;
         }
 
@@ -54,8 +62,8 @@ namespace Exam.Application
             base.ValidateRuleWithAdd();
             RuleFor(m => m.CreationTime).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamManage.CreationTime))));
 
-            RuleFor(m => m.Name).Must(v => _examManageRepository.Queryable().Any(x => x.Name == v)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamManage.Name))));
-            RuleFor(m => m.Code).Must(v => _examManageRepository.Queryable().Any(x => x.Code == v)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamManage.Code))));
+            RuleFor(m => m.Name).Must(v => !_examManageRepository.Queryable().Any(x => x.Name == v)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamManage.Name))));
+            RuleFor(m => m.Code).Must(v => !_examManageRepository.Queryable().Any(x => x.Code == v)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamManage.Code))));
         }
 
         protected override void ValidateRuleWithModify()
@@ -63,10 +71,15 @@ namespace Exam.Application
             base.ValidateRuleWithModify();
             RuleFor(m => m.LastModificationTime).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamManage.LastModificationTime))));
 
-            RuleFor(m => m.Name).Must((e,v) => _examManageRepository.Queryable().Any(x => x.Name == v && x.Id!=e.Id)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamManage.Name))));
-            RuleFor(m => m.Code).Must((e,v) => _examManageRepository.Queryable().Any(x => x.Code == v && x.Id != e.Id)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamManage.Code))));
+            RuleFor(m => m.Name).Must((e,v) => !_examManageRepository.Queryable().Any(x => x.Name == v && x.Id!=e.Id)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamManage.Name))));
+            RuleFor(m => m.Code).Must((e,v) => !_examManageRepository.Queryable().Any(x => x.Code == v && x.Id != e.Id)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamManage.Code))));
 
         }
 
+        private void ValidateRuleWithRemove()
+        {
+            RuleFor(x => x.Id).Must(v => !_examManageRepository.Queryable().Where(x => x.Id == v && x.ExamStatus != EExamStatus.NoStart).Any()).WithMessage(ExamErrorMessage.DeleteUnableExam);
+        }
+
     }
 }

+ 7 - 8
src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/ExamTagValidator.cs

@@ -13,7 +13,7 @@ using Hotline.Repository.SqlSugar.Validate;
 
 namespace Exam.Repository.Sqlsugar.Validators.ExamManages
 {
-    public class ExamTagValidator:BaseValidator<ExamTag>
+    public class ExamTagValidator : BaseValidator<ExamTag>
     {
         private readonly IExamTagRepository _examTagRepository;
         private readonly IDataPermissionFilterBuilder _dataPermissionFilterBuilder;
@@ -48,7 +48,6 @@ namespace Exam.Repository.Sqlsugar.Validators.ExamManages
         {
             base.BaseValidateRule();
             RuleFor(m => m.Name).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamTag.Name))));
-            RuleFor(m => m.ParentId).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamTag.ParentId))));
 
         }
 
@@ -56,22 +55,22 @@ namespace Exam.Repository.Sqlsugar.Validators.ExamManages
         {
             base.ValidateRuleWithAdd();
             RuleFor(m => m.CreationTime).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamTag.CreationTime))));
-             RuleFor(m => m.Name).Must(v => !_examTagRepository.Queryable().Any(x => x.Name == v)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamTag.Name))));
+            RuleFor(m => m.Name).Must(v => !_examTagRepository.Queryable().Any(x => x.Name == v)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamTag.Name))));
         }
 
         protected override void ValidateRuleWithModify()
         {
             base.ValidateRuleWithModify();
             RuleFor(m => m.LastModificationTime).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamTag.LastModificationTime))));
-             RuleFor(m => m.Name).Must((t, v) => !_examTagRepository.Queryable().Any(x => x.Name == v && x.Id != t.Id)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamTag.Name))));
+            RuleFor(m => m.Name).Must((t, v) => !_examTagRepository.Queryable().Any(x => x.Name == v && x.Id != t.Id)).WithMessage(x => string.Format(ErrorMessage.IsRepeat, x.GetType().GetDescription(nameof(ExamTag.Name))));
         }
 
         private void ValidateRuleWithRemove()
         {
-            RuleFor(m => m.Id).Must(v => !new ExamRepository<QuestionTag>(_examTagRepository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Any(x => x.TagId == v)).WithMessage(x => string.Format(ErrorMessage.IsRefrence, typeof(ExamTag).GetDescription(),x.Name));
-            RuleFor(m => m.Id).Must(v => !new ExamRepository<PracticeTag>(_examTagRepository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Any(x => x.TagId == v)).WithMessage(x => string.Format(ErrorMessage.IsRefrence, typeof(ExamTag).GetDescription(), x.Name));
-            RuleFor(m => m.Id).Must(v => !new ExamRepository<RuleTag>(_examTagRepository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Any(x => x.TagId == v)).WithMessage(x => string.Format(ErrorMessage.IsRefrence, typeof(ExamTag).GetDescription(), x.Name));
-            RuleFor(m => m.Id).Must(v => !new ExamRepository<TestPaperRuleTag>(_examTagRepository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Any(x => x.TagId == v)).WithMessage(x => string.Format(ErrorMessage.IsRefrence, typeof(ExamTag).GetDescription(), x.Name));
+            RuleFor(m => m.Id).Must(v => !new ExamRepository<QuestionTag>(_examTagRepository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Any(x => x.TagId == v)).WithMessage(x => string.Format(ErrorMessage.IsSimpleRefrence, typeof(ExamTag).GetDescription()));
+            RuleFor(m => m.Id).Must(v => !new ExamRepository<PracticeTag>(_examTagRepository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Any(x => x.TagId == v)).WithMessage(x => string.Format(ErrorMessage.IsSimpleRefrence, typeof(ExamTag).GetDescription()));
+            RuleFor(m => m.Id).Must(v => !new ExamRepository<RuleTag>(_examTagRepository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Any(x => x.TagId == v)).WithMessage(x => string.Format(ErrorMessage.IsSimpleRefrence, typeof(ExamTag).GetDescription()));
+            RuleFor(m => m.Id).Must(v => !new ExamRepository<TestPaperRuleTag>(_examTagRepository.UOW, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Any(x => x.TagId == v)).WithMessage(x => string.Format(ErrorMessage.IsSimpleRefrence, typeof(ExamTag).GetDescription()));
         }
 
     }

+ 52 - 0
src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/UserExamItemOptionsValidator.cs

@@ -0,0 +1,52 @@
+using Exam.ExamManages;
+using Exam.Infrastructure.Extensions;
+using Exam.Infrastructure.Validation.Validation;
+using Exam.Questions;
+using FluentValidation;
+using Hotline.Exams.ExamManages;
+using Hotline.Repository.SqlSugar.Validate;
+
+namespace Hotline.Repository.SqlSugar.Exam.Validators.ExamManages
+{
+    public class UserExamItemOptionsValidator: BaseValidator<UserExamItemOptions>
+    {
+        public UserExamItemOptionsValidator()
+        {
+            RuleSet(ValidatorTypeConstants.Create, () =>
+            {
+                BaseValidateRule();
+
+                ValidateRuleWithAdd();
+            });
+
+            RuleSet(ValidatorTypeConstants.Modify, () =>
+            {
+                BaseValidateRule();
+
+                ValidateRuleWithModify();
+            });
+        }
+
+        protected override void BaseValidateRule()
+        {
+            base.BaseValidateRule();
+
+            RuleFor(x => x.QuestionOptionId).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, typeof(QuestionOptions).GetDescription()));
+
+            RuleFor(x => x.UserExamItemId).NotEmpty().WithMessage(x=>string.Format(ErrorMessage.IsRequired,typeof(UserExamItem).GetDescription()));
+        }
+
+        protected override void ValidateRuleWithAdd()
+        {
+            base.ValidateRuleWithAdd();
+            RuleFor(m => m.CreationTime).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(UserExamItem.CreationTime))));
+        }
+
+        protected override void ValidateRuleWithModify()
+        {
+            base.ValidateRuleWithModify();
+            RuleFor(m => m.LastModificationTime).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(UserExamItem.LastModificationTime))));
+
+        }
+    }
+}

+ 6 - 0
src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/UserExamItemValidator.cs

@@ -1,6 +1,7 @@
 using Exam.ExamManages;
 using Exam.Infrastructure.Extensions;
 using Exam.Infrastructure.Validation.Validation;
+using Exam.Questions;
 using FluentValidation;
 using Hotline.Repository.SqlSugar.Validate;
 
@@ -28,6 +29,11 @@ namespace Exam.Repository.Sqlsugar.Validators.ExamManages
         protected override void BaseValidateRule()
         {
             base.BaseValidateRule();
+
+            RuleFor(x => x.UserExamId).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, typeof(UserExam).GetDescription()));
+
+            RuleFor(x => x.QuestionId).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, typeof(Question).GetDescription()));
+
         }
 
         protected override void ValidateRuleWithAdd()

+ 18 - 1
src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/UserExamValidator.cs

@@ -2,13 +2,17 @@ using Exam.ExamManages;
 using Exam.Infrastructure.Extensions;
 using Exam.Infrastructure.Validation.Validation;
 using FluentValidation;
+using Hotline.Repository.SqlSugar.Exam.Core.Constants;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages;
 using Hotline.Repository.SqlSugar.Validate;
 
 namespace Exam.Repository.Sqlsugar.Validators.ExamManages
 {
     public class UserExamValidator:BaseValidator<UserExam>
     {
-        public UserExamValidator()
+        private readonly IUserExamRepository _userExamRepository;
+
+        public UserExamValidator(IUserExamRepository userExamRepository)
         {
             RuleSet(ValidatorTypeConstants.Create, () =>
             {
@@ -23,6 +27,13 @@ namespace Exam.Repository.Sqlsugar.Validators.ExamManages
 
                 ValidateRuleWithModify();
             });
+
+            RuleSet(ValidatorTypeConstants.Remove, () =>
+            {              
+
+                ValidateRuleWithRemove();
+            });
+            this._userExamRepository = userExamRepository;
         }
 
         protected override void BaseValidateRule()
@@ -43,5 +54,11 @@ namespace Exam.Repository.Sqlsugar.Validators.ExamManages
 
         }
 
+        private void ValidateRuleWithRemove()
+        {
+            RuleFor(m => m.Id).Must(v=>!_userExamRepository.Queryable().Where(x=>x.Id == v && x.ExamStatus != Hotline.Share.Enums.Exams.EExamStatus.NoStart).Any()).WithMessage(ExamErrorMessage.DeleteUnableUserExam);
+
+        }
+
     }
 }

+ 31 - 0
src/Hotline.Share/Dtos/ExamManages/ExamQuestionDto.cs

@@ -0,0 +1,31 @@
+using Exam.Infrastructure.Data.Entity;
+using Hotline.Share.Enums.Exams;
+using System.ComponentModel;
+
+namespace Hotline.Share.Dtos.ExamManages
+{
+    /// <summary>
+    /// 考试试题
+    /// </summary>
+    [Description("考试试题")]
+    public class ExamQuestionDto : ActionRequest
+    {
+        /// <summary>
+        /// 题干
+        /// </summary>
+        [Description("题干")]
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 题型
+        /// </summary>
+        [Description("题型")]
+        public EQuestionType QuestionType { get; set; }
+
+        /// <summary>
+        /// 考试选项
+        /// </summary>
+        [Description("考试选项")]
+        public List<ExamQuestionOptionsDto> QuestionOptions {get;set;}
+    }
+}

+ 20 - 0
src/Hotline.Share/Dtos/ExamManages/ExamQuestionOptionsDto.cs

@@ -0,0 +1,20 @@
+using Exam.Infrastructure.Data.Entity;
+using System.ComponentModel;
+
+namespace Hotline.Share.Dtos.ExamManages
+{
+    public class ExamQuestionOptionsDto:ActionRequest
+    {
+        /// <summary>
+        /// 选项内容
+        /// </summary>
+        [Description("选项内容")]
+        public string Content { get; set; }
+
+        /// <summary>
+        /// 试题
+        /// </summary>
+        [Description("试题")]
+        public string QuestionId { get; set; }
+    }
+}

+ 22 - 0
src/Hotline.Share/Dtos/ExamManages/GradingExamDto.cs

@@ -0,0 +1,22 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Data.Interface;
+using System.ComponentModel;
+
+namespace Exam.Share.Dtos.ExamManage
+{
+    public class GradingExamDto : IActionRequest
+    {
+
+        /// <summary>
+        /// 是否已阅卷
+        /// </summary>
+        [Description("是否已阅卷")]
+        public bool IsCheck { get; set; }
+
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get ; set ; }
+    }
+}

+ 126 - 0
src/Hotline.Share/Dtos/ExamManages/GradingExamQuestionDto.cs

@@ -0,0 +1,126 @@
+using Exam.Infrastructure.Data.Interface;
+using Exam.Share;
+using Hotline.Share.Dtos.ExamManages;
+using Hotline.Share.Enums.Exams;
+using System.ComponentModel;
+
+namespace Exam.Application.Interface.Exam
+{
+    public class GradingExamQuestionDto : ExamQuestionDto
+    {
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get ; set; }
+
+        /// <summary>
+        /// 题干
+        /// </summary>
+        [Description("题干")]
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 答案
+        /// </summary>
+        [Description("答案")]
+        public string Answer { get; set; }
+
+        /// <summary>
+        /// 题型
+        /// </summary>
+        [Description("题型")]
+        public EQuestionType QuestionType { get; set; }
+
+        /// <summary>
+        /// 考试明细选项
+        /// </summary>
+        [Description("考试明细选项")]
+        public List<GradingUserExamItemOptionDto> UserExamItemOptionDtos { get; set; }
+
+    }
+
+    public class GradingExamQuestionTempDto
+    {
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 题干
+        /// </summary>
+        [Description("题干")]
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 答案
+        /// </summary>
+        [Description("答案")]
+        public string Answer { get; set; }
+
+        /// <summary>
+        /// 题型
+        /// </summary>
+        [Description("题型")]
+        public EQuestionType QuestionType { get; set; }
+
+        /// <summary>
+        /// 用户考试明细Id
+        /// </summary>
+        [Description("用户考试明细Id")]
+        public string UserExamItemId { get; set; }
+
+        /// <summary>
+        /// 试题选项Id
+        /// </summary>
+        [Description("试题选项Id")]
+        public string QuestionOptionId { get; set; }
+
+        /// <summary>
+        /// 选项内容
+        /// </summary>
+        [Description("选项内容")]
+        public string Content { get; set; }
+
+        /// <summary>
+        /// 是否答案
+        /// </summary>
+        [Description("是否答案")]
+        public bool IsAnswer { get; set; }
+    }
+
+    public class UserExamQuestionDto:IActionRequest
+    {
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 题干
+        /// </summary>
+        [Description("题干")]
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 答案
+        /// </summary>
+        [Description("答案")]
+        public string Answer { get; set; }
+
+        /// <summary>
+        /// 题型
+        /// </summary>
+        [Description("题型")]
+        public EQuestionType QuestionType { get; set; }
+
+        /// <summary>
+        /// 考试明细选项
+        /// </summary>
+        [Description("考试明细选项")]
+        public List<UserExamItemOptionDto> UserExamItemOptionDtos { get; set; }
+    }
+}

+ 0 - 39
src/Hotline.Share/Dtos/ExamManages/GradingExtamDto.cs

@@ -1,39 +0,0 @@
-using Exam.Infrastructure.Data.Entity;
-using System.ComponentModel;
-
-namespace Exam.Share.Dtos.ExamManage
-{
-    public class GradingExtamDto:ActionRequest
-    {
-
-        /// <summary>
-        /// 考试分数
-        /// </summary>
-        [Description("考试分数")]
-        public int Score { get; set; }
-
-        /// <summary>
-        /// 是否合格
-        /// </summary>
-        [Description("是否合格")]
-        public bool IsSuccess { get; set; }
-
-        /// <summary>
-        /// 考试状态
-        /// </summary>
-        [Description("考试状态")]
-        public int ExamStatus { get; set; }
-
-        /// <summary>
-        /// 是否已阅卷
-        /// </summary>
-        [Description("是否已阅卷")]
-        public int IsCheck { get; set; }
-
-        /// <summary>
-        /// 用户考试明细
-        /// </summary>
-        [Description("用户考试明细")]
-        public List<UserExamItemDto> UserExamItems { get; set; }
-    }
-}

+ 5 - 5
src/Hotline.Share/Dtos/ExamManages/GradingExtamItemDto.cs

@@ -1,15 +1,15 @@
-using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Data.Interface;
 using System.ComponentModel;
 
 namespace Exam.Share.Dtos.ExamManage
 {
-    public class GradingExtamItemDto:ActionRequest
+    public class GradingExtamItemDto:IAddRequest
     {
         /// <summary>
-        /// 用户考试Id
+        /// 用户考试明细Id
         /// </summary>
-        [Description("用户考试Id")]
-        public string UserExamId { get; set; }
+        [Description("用户考试明细Id")]
+        public string UserExamItemId { get; set; }
 
         /// <summary>
         /// 得分

+ 8 - 1
src/Hotline.Share/Dtos/ExamManages/SubmitExamDto.cs

@@ -1,9 +1,10 @@
 using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Data.Interface;
 using System.ComponentModel;
 
 namespace Exam.Share.Dtos.ExamManage
 {
-    public class SubmitExamDto: ActionRequest
+    public class SubmitExamDto: IActionRequest
     {     
 
         /// <summary>
@@ -11,5 +12,11 @@ namespace Exam.Share.Dtos.ExamManage
         /// </summary>
         [Description("是否交卷")]
         public int IsSubmit { get; set; }
+
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get; set; }
     }
 }

+ 13 - 30
src/Hotline.Share/Dtos/ExamManages/UserExamDto.cs

@@ -38,36 +38,6 @@ namespace Exam.Share
         [Description("考试Id")]
         public string ExamId { get; set; }
 
-        /// <summary>
-        /// 考试分数
-        /// </summary>
-        [Description("考试分数")]
-        public int Score { get; set; }
-
-        /// <summary>
-        /// 是否合格
-        /// </summary>
-        [Description("是否合格")]
-        public bool IsSuccess { get; set; }
-
-        /// <summary>
-        /// 考试状态
-        /// </summary>
-        [Description("考试状态")]
-        public EExamStatus ExamStatus { get; set; }
-
-        /// <summary>
-        /// 是否已阅卷
-        /// </summary>
-        [Description("是否已阅卷")]
-        public ECheck IsCheck { get; set; }
-
-        ///// <summary>
-        ///// 用户考试明细
-        ///// </summary>
-        //[Description("用户考试明细")]
-        //public List<AddUserExamItemDto> UserExamItems { get; set; }
-
         /// <summary>
         /// 操作状态        
         /// </summary>
@@ -93,4 +63,17 @@ namespace Exam.Share
         //[Description("用户考试明细")]
         //public new List<UpdateUserExamItemDto> UserExamItems { get; set; }
     }
+
+    /// <summary>
+    /// 开始考试
+    /// </summary>
+    [Description("开始考试")]
+    public class StartUserExamDto : IActionRequest
+    {
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get; set; }
+    }
 }

+ 37 - 5
src/Hotline.Share/Dtos/ExamManages/UserExamItemDto.cs

@@ -1,7 +1,8 @@
-using Exam.Infrastructure.Data.Entity;
-using Exam.Infrastructure.Data.Interface;
+using Exam.Infrastructure.Data.Interface;
 using Exam.Infrastructure.Enums;
+using Hotline.Share.Dtos.ExamManages;
 using Hotline.Share.Dtos.Questions;
+using Hotline.Share.Enums.Exams;
 using Hotline.Share.Exams.Interface;
 using System.ComponentModel;
 
@@ -14,10 +15,17 @@ namespace Exam.Share
     public class UserExamItemDto : UpdateUserExamItemDto
     {
         /// <summary>
-        /// 
+        /// 题
         /// </summary>
-        [Description("试题")]
-        public QuestionDto Question { get; set; }
+        [Description("题干")]
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 用户考试明细选项
+        /// </summary>
+        [Description("用户考试明细选项")]
+        public new List<UserExamItemOptionDto> UserExamItemOptionDtos { get; set; }
+
     }
 
     /// <summary>
@@ -38,11 +46,29 @@ namespace Exam.Share
         [Description("试题Id")]
         public string QuestionId { get; set; }
 
+        /// <summary>
+        /// 题型
+        /// </summary>
+        [Description("题型")]
+        public EQuestionType QuestionType { get; set; }
+
+        /// <summary>
+        /// 考试明细选项
+        /// </summary>
+        [Description("考试明细选项")]
+        public List<AddUserExamItemOptionDto> UserExamItemOptionDtos { get; set; }
+
         /// <summary>
         /// 操作状态        
         /// </summary>
         [Description("操作状态")]
         public EEOperationStatus OperationStatus { get; set; }
+
+        /// <summary>
+        /// 考试答案
+        /// </summary>
+        [Description("考试答案")]
+        public string Answer { get; set; }
     }
 
     /// <summary>
@@ -56,5 +82,11 @@ namespace Exam.Share
         /// </summary>
         [Description("主键")]
         public string Id { get; set; }
+
+        /// <summary>
+        /// 用户开始明细选项
+        /// </summary>
+        [Description("用户开始明细选项")]
+        public new List<UpdateUserExamItemOptionDto> UserExamItemOptionDtos { get; set; }
     }
 }

+ 54 - 0
src/Hotline.Share/Dtos/ExamManages/UserExamItemOptionDto.cs

@@ -0,0 +1,54 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Data.Interface;
+using Exam.Infrastructure.Enums;
+using Hotline.Share.Exams.Interface;
+using System.ComponentModel;
+
+namespace Hotline.Share.Dtos.ExamManages
+{
+    [Description("用户考试明细选项")]
+    public class UserExamItemOptionDto : UpdateUserExamItemOptionDto
+    {
+        /// <summary>
+        /// 选项内容
+        /// </summary>
+        [Description("选项内容")]
+        public string Content { get; set; }
+    }
+
+    public class GradingUserExamItemOptionDto : UserExamItemOptionDto
+    {
+        [Description("是否答案")]
+        public bool IsAnswer { get; set; }
+    }
+
+    public class AddUserExamItemOptionDto : IAddRequest, IOperationStatus
+    {
+        /// <summary>
+        /// 用户考试明细Id
+        /// </summary>
+        [Description("用户考试明细Id")]
+        public string UserExamItemId { get; set; }
+
+        /// <summary>
+        /// 试题选项Id
+        /// </summary>
+        [Description("试题选项Id")]
+        public string QuestionOptionId { get; set; }
+
+        /// <summary>
+        /// 操作状态
+        /// </summary>
+        [Description("操作状态")]
+        public EEOperationStatus OperationStatus { get; set; }
+    }
+
+    public class UpdateUserExamItemOptionDto : AddUserExamItemOptionDto, IActionRequest
+    {
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get; set; }
+    }
+}

+ 14 - 0
src/Hotline.Share/Requests/Exam/ExamQuestionGroupRequest.cs

@@ -0,0 +1,14 @@
+using Exam.Infrastructure.Data.Interface;
+using System.ComponentModel;
+
+namespace Hotline.Share.Requests.Exam
+{
+    public class ExamQuestionGroupRequest:IQueryRequest
+    {
+        /// <summary>
+        /// 考试Id
+        /// </summary>
+        [Description("考试Id")]
+        public string ExamId { get; set; }
+    }
+}

+ 14 - 0
src/Hotline.Share/Requests/Exam/ExamQuestionRequest.cs

@@ -0,0 +1,14 @@
+using Exam.Infrastructure.Data.Interface;
+using System.ComponentModel;
+
+namespace Hotline.Share.Requests.Exam
+{
+    public class ExamQuestionRequest : IQueryRequest
+    {
+        /// <summary>
+        /// 试题Id
+        /// </summary>
+        [Description("试题Id")]
+        public string QuestionId { get; set; }
+    }
+}

+ 21 - 0
src/Hotline.Share/Requests/Exam/GradingExamRequest.cs

@@ -0,0 +1,21 @@
+using Exam.Infrastructure.Data.Interface;
+using System.ComponentModel;
+
+namespace Hotline.Share.Requests.Exam
+{
+    public class GradingExamRequest:IQueryRequest
+    {
+        /// <summary>
+        /// 考试Id
+        /// </summary>
+        [Description("考试Id")]
+        public string ExamId { get; set; }
+
+        /// <summary>
+        /// 用户Id
+        /// </summary>
+        [Description("用户Id")]
+        public string UserId { get; set; }
+
+    }
+}

+ 59 - 9
src/Hotline.Share/Requests/Exam/UserExamPagedRequest.cs

@@ -1,27 +1,77 @@
 using Exam.Infrastructure.Data.Interface;
+using Hotline.Share.Enums.Exams;
+using System.ComponentModel;
+using System.Text.Json.Serialization;
 
 namespace Hotline.Share.Requests.Exam
 {
     public record UserExamPagedRequest:PagedRequest,IQueryRequest
     {
+        /// <summary>
+        /// 考试名称
+        /// </summary>
+        [Description("考试名称")]
         public string Name { get; set; }
 
-        public int Type { get; set; }
+        /// <summary>
+        /// 考试类型
+        /// </summary>
+        [Description("考试类型")]
+        public EExamType? ExamType { get; set; }
 
-        public DateTime MinStartTime { get; set; }
+        /// <summary>
+        /// 最小开始时间
+        /// </summary>
+        [Description("最小开始时间")]
+        public DateTime? MinStartTime { get; set; }
 
-        public DateTime MaxStartTime { get; set; }
+        /// <summary>
+        /// 最大开始时间
+        /// </summary>
+        [Description("最大开始时间")]
+        public DateTime? MaxStartTime { get; set; }
 
-        public DateTime MinEndTime { get; set; }
+        /// <summary>
+        /// 最小结束时间
+        /// </summary>
+        [Description("最小结束时间")]
+        public DateTime? MinEndTime { get; set; }
 
-        public DateTime MaxEndTime { get; set; }
+        /// <summary>
+        /// 最大结束时间
+        /// </summary>
+        [Description("最大结束时间")]
+        public DateTime? MaxEndTime { get; set; }
 
-        public int MinTotalScore { get; set; }
+        /// <summary>
+        /// 最小总分
+        /// </summary>
+        [Description("最小总分")]
+        public int? MinTotalScore { get; set; }
 
-        public int MaxTotalScore { get; set; }
+        /// <summary>
+        /// 最大总分
+        /// </summary>
+        [Description("最大总分")]
+        public int? MaxTotalScore { get; set; }
 
-        public int MinScore { get; set; }
+        /// <summary>
+        /// 最小分数
+        /// </summary>
+        [Description("最小分数")]
+        public int? MinScore { get; set; }
 
-        public int MaxScore { get; set; }
+        /// <summary>
+        /// 最大分数
+        /// </summary>
+        [Description("最大分数")]
+        public int? MaxScore { get; set; }
+
+        /// <summary>
+        /// 当前用户
+        /// </summary>
+        [Description("当前用户Id")]
+        [JsonIgnore]
+        public string UserId { get; set; }
     }
 }

+ 5 - 3
src/Hotline.Share/Requests/Question/QuestionPagedRequest.cs

@@ -1,6 +1,7 @@
 using Exam.Infrastructure.Data.Interface;
 using Hotline.Share.Enums.Exams;
 using System.ComponentModel;
+using System.Text.Json.Serialization;
 
 namespace Hotline.Share.Requests.Question
 {
@@ -10,7 +11,7 @@ namespace Hotline.Share.Requests.Question
         /// 标签
         /// </summary>
         [Description("标签")]
-        public string TagId { get; set; }
+        public List<string> TagIds { get; set; }
 
         /// <summary>
         /// 试题难度
@@ -25,9 +26,10 @@ namespace Hotline.Share.Requests.Question
         public string Title { get; set; }
 
         /// <summary>
-        /// 知识集合
+        /// 知识库Id
         /// </summary>
-        [Description("知识集合")]
+        [Description("知识库Id")]
+        [JsonIgnore]
         public List<string> KnowladgeIds { get; set; }
     }
 }

+ 14 - 0
src/Hotline.Share/Requests/Question/QuestionRequest.cs

@@ -0,0 +1,14 @@
+using Exam.Infrastructure.Data.Interface;
+using System.ComponentModel;
+
+namespace Hotline.Share.Requests.Question
+{
+    public class QuestionRequest:IQueryRequest
+    {
+        /// <summary>
+        /// 知识库Id
+        /// </summary>
+        [Description("知识库Id")]
+        public List<string> KnowladgeIds { get; set; }
+    }
+}

+ 27 - 0
src/Hotline.Share/ViewResponses/Exam/ExamQuestionViewResponse.cs

@@ -0,0 +1,27 @@
+using Exam.Infrastructure.Data.Interface;
+using Hotline.Share.Enums.Exams;
+using System.ComponentModel;
+
+namespace Hotline.Share.ViewResponses.Exam
+{
+    public class ExamQuestionViewResponse : IViewResponse
+    {
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 题型
+        /// </summary>
+        [Description("题型")]
+        public EQuestionType QuestionType { get; set; }
+
+        /// <summary>
+        /// 试题列表
+        /// </summary>
+        [Description("试题列表")]
+        public List<IViewResponse> Questions { get; set; }
+    }
+}

+ 14 - 1
src/Hotline.Share/ViewResponses/Exam/UserExamResultViewResponse.cs

@@ -1,4 +1,5 @@
 using Exam.Infrastructure.Data.Interface;
+using Hotline.Share.Enums.Exams;
 using System.ComponentModel;
 
 namespace Exam.Share.ViewResponses.Exam
@@ -45,7 +46,7 @@ namespace Exam.Share.ViewResponses.Exam
         /// 状态
         /// </summary>
         [Description("状态")]
-        public int Status { get; set; }
+        public EPublicStatus Status { get; set; }
 
         /// <summary>
         /// 排序
@@ -59,5 +60,17 @@ namespace Exam.Share.ViewResponses.Exam
         [Description("主键")]
 
         public string Id { get; set; }
+
+        /// <summary>
+        /// 考试状态
+        /// </summary>
+        [Description("考试状态")]
+        public EExamStatus ExamStatus { get; set; }
+
+        /// <summary>
+        /// 是否考试合格
+        /// </summary>
+        [Description("是否考试合格")]
+        public bool IsSuccess { get; set; }
     }
 }

+ 14 - 0
src/Hotline.Share/ViewResponses/SimpleViewResponse.cs

@@ -0,0 +1,14 @@
+using Exam.Infrastructure.Data.Interface;
+using System.ComponentModel;
+
+namespace Hotline.Share.ViewResponses
+{
+    public class SimpleViewResponse:IViewResponse
+    {
+        /// <summary>
+        /// 主键
+        /// </summary>
+        [Description("主键")]
+        public string Id { get; set; }
+    }
+}

+ 1 - 8
src/Hotline/Exams/ExamManages/ExamAnswer.cs

@@ -10,13 +10,6 @@ namespace Exam.ExamManages
     [Description("考试答案")]
     public class ExamAnswer : BusinessEntity
     {
-        /// <summary>
-        /// 选项
-        /// </summary>
-        [SugarColumn(ColumnDescription = "选项")]
-        [Description("选项")]
-        public string? OptionId { get; set; }
-
         /// <summary>
         /// 答案
         /// </summary>
@@ -36,6 +29,6 @@ namespace Exam.ExamManages
         /// </summary>
         [SugarColumn(ColumnDescription = "考试Id")]
         [Description("考试Id")]
-        public string ExamId { get; set; }
+        public string UserExamItemId { get; set; }
     }
 }

+ 28 - 0
src/Hotline/Exams/ExamManages/UserExamItemOptions.cs

@@ -0,0 +1,28 @@
+using Hotline.Exams.Base;
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.Exams.ExamManages
+{
+    /// <summary>
+    /// 用户考试选项
+    /// </summary>
+    [Description("用户考试选项")]
+    public class UserExamItemOptions: BusinessEntity
+    {
+        /// <summary>
+        /// 用户考试明细Id
+        /// </summary>
+        [Description("用户考试明细Id")]
+        [SugarColumn(ColumnDescription = "用户考试明细Id")]
+        public string UserExamItemId { get; set; }
+
+        /// <summary>
+        /// 用户考试明细选项Id
+        /// </summary>
+        [Description("用户考试明细选项Id")]
+        [SugarColumn(ColumnDescription = "用户考试明细选项Id")]
+        public string QuestionOptionId { get; set; }
+    }
+}

+ 2 - 0
src/Hotline/Exams/Validate/ErrorMessage.cs

@@ -103,6 +103,8 @@
         public const string RequestFail = "请求失败!";
 
         public const string Greater = "{0}不能大于{1}";
+
+        public const string IsNotExists = "{0}不存在";
     }
 
     public static class RegexConstant