From 43d2e57d44aec07184e38952483ae615f8ec49c7 Mon Sep 17 00:00:00 2001 From: zhangzhe Date: Tue, 3 Feb 2026 15:47:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .kiro/specs/admin-business-modules/design.md | 1117 +++++++++++++++++ .../admin-business-modules/requirements.md | 263 ++++ .kiro/specs/admin-business-modules/tasks.md | 306 +++++ 3 files changed, 1686 insertions(+) create mode 100644 .kiro/specs/admin-business-modules/design.md create mode 100644 .kiro/specs/admin-business-modules/requirements.md create mode 100644 .kiro/specs/admin-business-modules/tasks.md diff --git a/.kiro/specs/admin-business-modules/design.md b/.kiro/specs/admin-business-modules/design.md new file mode 100644 index 0000000..6b1ba33 --- /dev/null +++ b/.kiro/specs/admin-business-modules/design.md @@ -0,0 +1,1117 @@ +# Design Document: Admin Business Modules + +## Overview + +本设计文档描述了学业邑规划 MiAssessment 管理后台业务模块的技术架构和实现方案。系统基于 .NET 10 + SQL Server 2022 技术栈,采用分层架构设计,在现有的 MiAssessment.Admin.Business 项目基础上扩展7个核心业务模块。 + +### 设计目标 + +1. **模块化设计**: 每个业务模块独立封装,便于维护和扩展 +2. **统一接口风格**: 采用 RPC 风格,仅使用 GET/POST 请求 +3. **权限集成**: 与现有权限系统无缝集成 +4. **高性能**: 支持分页查询、缓存优化 +5. **可追溯**: 软删除、操作日志记录 + +## Architecture + +### 系统架构图 + +```mermaid +graph TB + subgraph "Presentation Layer" + A[Admin Frontend] + end + + subgraph "API Layer - MiAssessment.Admin" + B[Controllers] + B1[ConfigController] + B2[ContentController] + B3[AssessmentController] + B4[UserController] + B5[OrderController] + B6[PlannerController] + B7[DistributionController] + B8[DashboardController] + end + + subgraph "Business Layer - MiAssessment.Admin.Business" + C[Services] + C1[ConfigService] + C2[ContentService] + C3[AssessmentService] + C4[UserBusinessService] + C5[OrderService] + C6[PlannerService] + C7[DistributionService] + C8[DashboardService] + end + + subgraph "Data Layer" + D[AdminBusinessDbContext] + E[(SQL Server)] + end + + A --> B + B --> C + C --> D + D --> E +``` + +### 项目结构 + +``` +server/MiAssessment/src/MiAssessment.Admin.Business/ +├── Controllers/ +│ ├── BusinessControllerBase.cs # 基础控制器 +│ ├── ConfigController.cs # 系统配置 +│ ├── ContentController.cs # 内容管理(轮播图、宣传图) +│ ├── AssessmentController.cs # 测评管理 +│ ├── UserController.cs # 用户管理 +│ ├── OrderController.cs # 订单管理 +│ ├── PlannerController.cs # 规划师管理 +│ ├── DistributionController.cs # 分销管理 +│ └── DashboardController.cs # 数据统计 +├── Services/ +│ ├── Interfaces/ +│ │ ├── IConfigService.cs +│ │ ├── IContentService.cs +│ │ ├── IAssessmentService.cs +│ │ ├── IUserBusinessService.cs +│ │ ├── IOrderService.cs +│ │ ├── IPlannerService.cs +│ │ ├── IDistributionService.cs +│ │ └── IDashboardService.cs +│ ├── ConfigService.cs +│ ├── ContentService.cs +│ ├── AssessmentService.cs +│ ├── UserBusinessService.cs +│ ├── OrderService.cs +│ ├── PlannerService.cs +│ ├── DistributionService.cs +│ └── DashboardService.cs +├── Models/ +│ ├── Config/ +│ ├── Content/ +│ ├── Assessment/ +│ ├── User/ +│ ├── Order/ +│ ├── Planner/ +│ ├── Distribution/ +│ └── Dashboard/ +├── Entities/ +│ └── (Entity classes mapping to database tables) +└── Data/ + └── AdminBusinessDbContext.cs +``` + +## Components and Interfaces + +### 1. 系统配置模块 (Config Module) + +#### Controller: ConfigController + +```csharp +[Route("api/admin/config")] +public class ConfigController : BusinessControllerBase +{ + // GET /api/admin/config/getList - 获取配置列表 + [HttpGet("getList")] + Task>> GetList(); + + // POST /api/admin/config/update - 更新配置 + [HttpPost("update")] + Task> Update(UpdateConfigRequest request); + + // GET /api/admin/config/getByKey - 根据Key获取配置 + [HttpGet("getByKey")] + Task> GetByKey(string configKey); +} +``` + +#### Service Interface: IConfigService + +```csharp +public interface IConfigService +{ + Task> GetConfigListAsync(); + Task GetConfigByKeyAsync(string configKey); + Task UpdateConfigAsync(string configKey, string configValue); + Task ValidateConfigValueAsync(string configKey, string configValue); +} +``` + +### 2. 内容管理模块 (Content Module) + +#### Controller: ContentController + +```csharp +[Route("api/admin/content")] +public class ContentController : BusinessControllerBase +{ + // Banner APIs + [HttpGet("banner/getList")] + Task>> GetBannerList(BannerQueryRequest request); + + [HttpPost("banner/create")] + Task> CreateBanner(CreateBannerRequest request); + + [HttpPost("banner/update")] + Task> UpdateBanner(UpdateBannerRequest request); + + [HttpPost("banner/delete")] + Task> DeleteBanner(long id); + + [HttpPost("banner/updateStatus")] + Task> UpdateBannerStatus(UpdateStatusRequest request); + + [HttpPost("banner/updateSort")] + Task> UpdateBannerSort(UpdateSortRequest request); + + // Promotion APIs + [HttpGet("promotion/getList")] + Task>> GetPromotionList(PromotionQueryRequest request); + + [HttpPost("promotion/create")] + Task> CreatePromotion(CreatePromotionRequest request); + + [HttpPost("promotion/update")] + Task> UpdatePromotion(UpdatePromotionRequest request); + + [HttpPost("promotion/delete")] + Task> DeletePromotion(long id); + + [HttpPost("promotion/updateStatus")] + Task> UpdatePromotionStatus(UpdateStatusRequest request); +} +``` + +#### Service Interface: IContentService + +```csharp +public interface IContentService +{ + // Banner operations + Task> GetBannerListAsync(BannerQueryRequest request); + Task GetBannerByIdAsync(long id); + Task CreateBannerAsync(CreateBannerRequest request); + Task UpdateBannerAsync(UpdateBannerRequest request); + Task DeleteBannerAsync(long id); + Task UpdateBannerStatusAsync(long id, int status); + Task UpdateBannerSortAsync(List items); + + // Promotion operations + Task> GetPromotionListAsync(PromotionQueryRequest request); + Task GetPromotionByIdAsync(long id); + Task CreatePromotionAsync(CreatePromotionRequest request); + Task UpdatePromotionAsync(UpdatePromotionRequest request); + Task DeletePromotionAsync(long id); + Task UpdatePromotionStatusAsync(long id, int status); +} +``` + +### 3. 测评管理模块 (Assessment Module) + +#### Controller: AssessmentController + +```csharp +[Route("api/admin/assessment")] +public class AssessmentController : BusinessControllerBase +{ + // Assessment Type APIs + [HttpGet("type/getList")] + Task>> GetTypeList(AssessmentTypeQueryRequest request); + + [HttpPost("type/create")] + Task> CreateType(CreateAssessmentTypeRequest request); + + [HttpPost("type/update")] + Task> UpdateType(UpdateAssessmentTypeRequest request); + + [HttpPost("type/delete")] + Task> DeleteType(long id); + + // Question APIs + [HttpGet("question/getList")] + Task>> GetQuestionList(QuestionQueryRequest request); + + [HttpPost("question/create")] + Task> CreateQuestion(CreateQuestionRequest request); + + [HttpPost("question/update")] + Task> UpdateQuestion(UpdateQuestionRequest request); + + [HttpPost("question/delete")] + Task> DeleteQuestion(long id); + + [HttpPost("question/batchImport")] + Task> BatchImportQuestions(BatchImportQuestionsRequest request); + + // Report Category APIs + [HttpGet("category/getTree")] + Task>> GetCategoryTree(long assessmentTypeId); + + [HttpPost("category/create")] + Task> CreateCategory(CreateCategoryRequest request); + + [HttpPost("category/update")] + Task> UpdateCategory(UpdateCategoryRequest request); + + [HttpPost("category/delete")] + Task> DeleteCategory(long id); + + // Question-Category Mapping APIs + [HttpGet("mapping/getByQuestion")] + Task>> GetMappingsByQuestion(long questionId); + + [HttpGet("mapping/getByCategory")] + Task>> GetMappingsByCategory(long categoryId); + + [HttpPost("mapping/batchUpdate")] + Task> BatchUpdateMappings(BatchUpdateMappingsRequest request); + + // Report Conclusion APIs + [HttpGet("conclusion/getList")] + Task>> GetConclusionList(long categoryId); + + [HttpPost("conclusion/create")] + Task> CreateConclusion(CreateConclusionRequest request); + + [HttpPost("conclusion/update")] + Task> UpdateConclusion(UpdateConclusionRequest request); + + [HttpPost("conclusion/delete")] + Task> DeleteConclusion(long id); +} +``` + +### 4. 用户管理模块 (User Module) + +#### Controller: UserController + +```csharp +[Route("api/admin/user")] +public class UserController : BusinessControllerBase +{ + [HttpGet("getList")] + Task>> GetList(UserQueryRequest request); + + [HttpGet("getDetail")] + Task> GetDetail(long id); + + [HttpPost("updateStatus")] + Task> UpdateStatus(UpdateUserStatusRequest request); + + [HttpPost("updateLevel")] + Task> UpdateLevel(UpdateUserLevelRequest request); + + [HttpGet("export")] + Task Export(UserQueryRequest request); +} +``` + +### 5. 订单管理模块 (Order Module) + +#### Controller: OrderController + +```csharp +[Route("api/admin/order")] +public class OrderController : BusinessControllerBase +{ + [HttpGet("getList")] + Task>> GetList(OrderQueryRequest request); + + [HttpGet("getDetail")] + Task> GetDetail(long id); + + [HttpPost("refund")] + Task> Refund(RefundRequest request); + + [HttpGet("export")] + Task Export(OrderQueryRequest request); +} +``` + +### 6. 规划师管理模块 (Planner Module) + +#### Controller: PlannerController + +```csharp +[Route("api/admin/planner")] +public class PlannerController : BusinessControllerBase +{ + // Planner APIs + [HttpGet("getList")] + Task>> GetList(PlannerQueryRequest request); + + [HttpPost("create")] + Task> Create(CreatePlannerRequest request); + + [HttpPost("update")] + Task> Update(UpdatePlannerRequest request); + + [HttpPost("delete")] + Task> Delete(long id); + + [HttpPost("updateStatus")] + Task> UpdateStatus(UpdateStatusRequest request); + + [HttpPost("updateSort")] + Task> UpdateSort(UpdateSortRequest request); + + // Booking APIs + [HttpGet("booking/getList")] + Task>> GetBookingList(BookingQueryRequest request); + + [HttpGet("booking/getDetail")] + Task> GetBookingDetail(long id); + + [HttpPost("booking/updateStatus")] + Task> UpdateBookingStatus(UpdateBookingStatusRequest request); + + [HttpGet("booking/export")] + Task ExportBookings(BookingQueryRequest request); +} +``` + +### 7. 分销管理模块 (Distribution Module) + +#### Controller: DistributionController + +```csharp +[Route("api/admin/distribution")] +public class DistributionController : BusinessControllerBase +{ + // Invite Code APIs + [HttpGet("inviteCode/getList")] + Task>> GetInviteCodeList(InviteCodeQueryRequest request); + + [HttpPost("inviteCode/generate")] + Task> GenerateInviteCodes(GenerateInviteCodesRequest request); + + [HttpPost("inviteCode/assign")] + Task> AssignInviteCodes(AssignInviteCodesRequest request); + + [HttpGet("inviteCode/export")] + Task ExportInviteCodes(InviteCodeQueryRequest request); + + // Commission APIs + [HttpGet("commission/getList")] + Task>> GetCommissionList(CommissionQueryRequest request); + + [HttpGet("commission/getDetail")] + Task> GetCommissionDetail(long id); + + [HttpGet("commission/getStatistics")] + Task> GetCommissionStatistics(CommissionStatisticsRequest request); + + [HttpGet("commission/export")] + Task ExportCommissions(CommissionQueryRequest request); + + // Withdrawal APIs + [HttpGet("withdrawal/getList")] + Task>> GetWithdrawalList(WithdrawalQueryRequest request); + + [HttpGet("withdrawal/getDetail")] + Task> GetWithdrawalDetail(long id); + + [HttpPost("withdrawal/approve")] + Task> ApproveWithdrawal(ApproveWithdrawalRequest request); + + [HttpPost("withdrawal/reject")] + Task> RejectWithdrawal(RejectWithdrawalRequest request); + + [HttpPost("withdrawal/complete")] + Task> CompleteWithdrawal(CompleteWithdrawalRequest request); + + [HttpGet("withdrawal/export")] + Task ExportWithdrawals(WithdrawalQueryRequest request); +} +``` + +### 8. 数据统计模块 (Dashboard Module) + +#### Controller: DashboardController + +```csharp +[Route("api/admin/dashboard")] +public class DashboardController : BusinessControllerBase +{ + [HttpGet("getOverview")] + Task> GetOverview(); + + [HttpGet("getTrends")] + Task> GetTrends(TrendsQueryRequest request); + + [HttpGet("getPendingItems")] + Task> GetPendingItems(); +} +``` + +## Data Models + +### 通用模型 + +```csharp +// 分页请求基类 +public class PagedRequest +{ + public int PageIndex { get; set; } = 1; + public int PageSize { get; set; } = 20; +} + +// 分页结果 +public class PagedResult +{ + public List Items { get; set; } + public int Total { get; set; } + public int PageIndex { get; set; } + public int PageSize { get; set; } + public int TotalPages => (int)Math.Ceiling(Total / (double)PageSize); +} + +// 统一响应格式 +public class ApiResponse +{ + public int Code { get; set; } + public string Message { get; set; } + public T Data { get; set; } +} + +// 状态更新请求 +public class UpdateStatusRequest +{ + public long Id { get; set; } + public int Status { get; set; } +} + +// 排序更新请求 +public class UpdateSortRequest +{ + public List Items { get; set; } +} + +public class SortItem +{ + public long Id { get; set; } + public int Sort { get; set; } +} +``` + +### 配置模块模型 + +```csharp +public class ConfigDto +{ + public long Id { get; set; } + public string ConfigKey { get; set; } + public string ConfigValue { get; set; } + public string ConfigType { get; set; } + public string Description { get; set; } +} + +public class ConfigGroupDto +{ + public string ConfigType { get; set; } + public string TypeName { get; set; } + public List Items { get; set; } +} + +public class UpdateConfigRequest +{ + public string ConfigKey { get; set; } + public string ConfigValue { get; set; } +} +``` + +### 内容模块模型 + +```csharp +public class BannerDto +{ + public long Id { get; set; } + public string Title { get; set; } + public string ImageUrl { get; set; } + public int LinkType { get; set; } + public string LinkTypeName { get; set; } + public string LinkUrl { get; set; } + public string AppId { get; set; } + public int Sort { get; set; } + public int Status { get; set; } + public string StatusName { get; set; } + public DateTime CreateTime { get; set; } +} + +public class CreateBannerRequest +{ + public string Title { get; set; } + public string ImageUrl { get; set; } + public int LinkType { get; set; } + public string LinkUrl { get; set; } + public string AppId { get; set; } + public int Sort { get; set; } + public int Status { get; set; } = 1; +} + +public class PromotionDto +{ + public long Id { get; set; } + public string Title { get; set; } + public string ImageUrl { get; set; } + public int Position { get; set; } + public string PositionName { get; set; } + public int Sort { get; set; } + public int Status { get; set; } + public string StatusName { get; set; } + public DateTime CreateTime { get; set; } +} +``` + +### 测评模块模型 + +```csharp +public class AssessmentTypeDto +{ + public long Id { get; set; } + public string Name { get; set; } + public string Code { get; set; } + public string ImageUrl { get; set; } + public string IntroContent { get; set; } + public decimal Price { get; set; } + public int QuestionCount { get; set; } + public int Sort { get; set; } + public int Status { get; set; } + public string StatusName { get; set; } + public DateTime CreateTime { get; set; } +} + +public class QuestionDto +{ + public long Id { get; set; } + public long AssessmentTypeId { get; set; } + public string AssessmentTypeName { get; set; } + public int QuestionNo { get; set; } + public string Content { get; set; } + public int Sort { get; set; } + public int Status { get; set; } + public List CategoryIds { get; set; } +} + +public class CategoryTreeNode +{ + public long Id { get; set; } + public long ParentId { get; set; } + public string Name { get; set; } + public string Code { get; set; } + public int CategoryType { get; set; } + public string CategoryTypeName { get; set; } + public int ScoreRule { get; set; } + public string ScoreRuleName { get; set; } + public int Sort { get; set; } + public List Children { get; set; } +} + +public class ConclusionDto +{ + public long Id { get; set; } + public long CategoryId { get; set; } + public string CategoryName { get; set; } + public int ConclusionType { get; set; } + public string ConclusionTypeName { get; set; } + public string Title { get; set; } + public string Content { get; set; } +} +``` + +### 用户模块模型 + +```csharp +public class UserDto +{ + public long Id { get; set; } + public string Uid { get; set; } + public string Phone { get; set; } + public string Nickname { get; set; } + public string Avatar { get; set; } + public int UserLevel { get; set; } + public string UserLevelName { get; set; } + public decimal Balance { get; set; } + public decimal TotalIncome { get; set; } + public int Status { get; set; } + public string StatusName { get; set; } + public DateTime CreateTime { get; set; } + public DateTime? LastLoginTime { get; set; } +} + +public class UserDetailDto : UserDto +{ + public long? ParentUserId { get; set; } + public string ParentUserNickname { get; set; } + public string ParentUserUid { get; set; } + public decimal WithdrawnAmount { get; set; } + public int OrderCount { get; set; } + public int AssessmentCount { get; set; } + public int InviteCount { get; set; } +} + +public class UserQueryRequest : PagedRequest +{ + public string Uid { get; set; } + public string Phone { get; set; } + public string Nickname { get; set; } + public int? UserLevel { get; set; } + public int? Status { get; set; } + public DateTime? CreateTimeStart { get; set; } + public DateTime? CreateTimeEnd { get; set; } +} +``` + +### 订单模块模型 + +```csharp +public class OrderDto +{ + public long Id { get; set; } + public string OrderNo { get; set; } + public long UserId { get; set; } + public string UserNickname { get; set; } + public string UserPhone { get; set; } + public int OrderType { get; set; } + public string OrderTypeName { get; set; } + public string ProductName { get; set; } + public decimal Amount { get; set; } + public decimal PayAmount { get; set; } + public int? PayType { get; set; } + public string PayTypeName { get; set; } + public int Status { get; set; } + public string StatusName { get; set; } + public DateTime? PayTime { get; set; } + public DateTime CreateTime { get; set; } +} + +public class OrderDetailDto : OrderDto +{ + public long ProductId { get; set; } + public long? InviteCodeId { get; set; } + public string InviteCode { get; set; } + public string TransactionId { get; set; } + public DateTime? RefundTime { get; set; } + public decimal? RefundAmount { get; set; } + public string RefundReason { get; set; } + public string Remark { get; set; } + // 关联的测评记录或预约记录 + public object RelatedRecord { get; set; } +} + +public class RefundRequest +{ + public long OrderId { get; set; } + public decimal RefundAmount { get; set; } + public string RefundReason { get; set; } +} +``` + +### 分销模块模型 + +```csharp +public class InviteCodeDto +{ + public long Id { get; set; } + public string Code { get; set; } + public string BatchNo { get; set; } + public long? AssignUserId { get; set; } + public string AssignUserNickname { get; set; } + public DateTime? AssignTime { get; set; } + public long? UseUserId { get; set; } + public string UseUserNickname { get; set; } + public long? UseOrderId { get; set; } + public DateTime? UseTime { get; set; } + public int Status { get; set; } + public string StatusName { get; set; } + public DateTime CreateTime { get; set; } +} + +public class GenerateInviteCodesRequest +{ + public int Count { get; set; } +} + +public class GenerateResult +{ + public string BatchNo { get; set; } + public int Count { get; set; } + public List Codes { get; set; } +} + +public class CommissionDto +{ + public long Id { get; set; } + public long UserId { get; set; } + public string UserNickname { get; set; } + public long FromUserId { get; set; } + public string FromUserNickname { get; set; } + public long OrderId { get; set; } + public string OrderNo { get; set; } + public decimal OrderAmount { get; set; } + public decimal CommissionRate { get; set; } + public decimal CommissionAmount { get; set; } + public int Level { get; set; } + public string LevelName { get; set; } + public int Status { get; set; } + public string StatusName { get; set; } + public DateTime? SettleTime { get; set; } + public DateTime CreateTime { get; set; } +} + +public class CommissionStatisticsDto +{ + public decimal TotalAmount { get; set; } + public decimal PendingAmount { get; set; } + public decimal SettledAmount { get; set; } + public int TotalCount { get; set; } + public int PendingCount { get; set; } + public int SettledCount { get; set; } +} + +public class WithdrawalDto +{ + public long Id { get; set; } + public string WithdrawalNo { get; set; } + public long UserId { get; set; } + public string UserNickname { get; set; } + public string UserPhone { get; set; } + public decimal Amount { get; set; } + public decimal BeforeBalance { get; set; } + public decimal AfterBalance { get; set; } + public int Status { get; set; } + public string StatusName { get; set; } + public long? AuditUserId { get; set; } + public string AuditUserName { get; set; } + public DateTime? AuditTime { get; set; } + public string AuditRemark { get; set; } + public DateTime? PayTime { get; set; } + public string PayTransactionId { get; set; } + public DateTime CreateTime { get; set; } +} +``` + +### 仪表盘模块模型 + +```csharp +public class DashboardOverviewDto +{ + public TodayStatistics Today { get; set; } + public TotalStatistics Total { get; set; } +} + +public class TodayStatistics +{ + public int NewUsers { get; set; } + public int NewOrders { get; set; } + public decimal Revenue { get; set; } +} + +public class TotalStatistics +{ + public int TotalUsers { get; set; } + public int TotalOrders { get; set; } + public decimal TotalRevenue { get; set; } +} + +public class TrendsDto +{ + public List UserTrend { get; set; } + public List OrderTrend { get; set; } + public List RevenueTrend { get; set; } +} + +public class TrendItem +{ + public string Date { get; set; } + public decimal Value { get; set; } +} + +public class PendingItemsDto +{ + public int PendingWithdrawals { get; set; } + public int PendingBookings { get; set; } +} +``` + + + +## Error Handling + +### 错误码定义 + +```csharp +public static class ErrorCodes +{ + // 通用错误 (1000-1999) + public const int Success = 0; + public const int ParamError = 1001; + public const int Unauthorized = 1002; + public const int TokenExpired = 1003; + public const int Forbidden = 1004; + + // 业务错误 (2000-2999) + public const int BusinessError = 2001; + public const int DataNotFound = 2002; + public const int DataExists = 2003; + public const int DataInUse = 2004; + public const int InvalidOperation = 2005; + + // 配置模块错误 (3000-3099) + public const int ConfigNotFound = 3001; + public const int ConfigValueInvalid = 3002; + + // 内容模块错误 (3100-3199) + public const int BannerNotFound = 3101; + public const int BannerImageRequired = 3102; + public const int BannerLinkUrlRequired = 3103; + public const int PromotionNotFound = 3111; + public const int PromotionImageRequired = 3112; + + // 测评模块错误 (3200-3299) + public const int AssessmentTypeNotFound = 3201; + public const int AssessmentTypeCodeExists = 3202; + public const int QuestionNotFound = 3211; + public const int QuestionNoExists = 3212; + public const int CategoryNotFound = 3221; + public const int CategoryHasChildren = 3222; + public const int ConclusionNotFound = 3231; + + // 用户模块错误 (3300-3399) + public const int UserNotFound = 3301; + + // 订单模块错误 (3400-3499) + public const int OrderNotFound = 3401; + public const int OrderCannotRefund = 3402; + public const int RefundAmountInvalid = 3403; + + // 规划师模块错误 (3500-3599) + public const int PlannerNotFound = 3501; + public const int BookingNotFound = 3511; + + // 分销模块错误 (3600-3699) + public const int InviteCodeNotFound = 3601; + public const int InviteCodeAlreadyAssigned = 3602; + public const int CommissionNotFound = 3611; + public const int WithdrawalNotFound = 3621; + public const int WithdrawalCannotApprove = 3622; + public const int WithdrawalCannotReject = 3623; + public const int WithdrawalCannotComplete = 3624; + + // 系统错误 (5000-5999) + public const int SystemError = 5000; +} +``` + +### 异常处理 + +```csharp +public class BusinessException : Exception +{ + public int Code { get; } + + public BusinessException(int code, string message) : base(message) + { + Code = code; + } +} + +// 全局异常过滤器 +public class GlobalExceptionFilter : IExceptionFilter +{ + public void OnException(ExceptionContext context) + { + if (context.Exception is BusinessException bizEx) + { + context.Result = new JsonResult(new ApiResponse + { + Code = bizEx.Code, + Message = bizEx.Message, + Data = null + }); + } + else + { + // 记录日志 + _logger.LogError(context.Exception, "Unhandled exception"); + + context.Result = new JsonResult(new ApiResponse + { + Code = ErrorCodes.SystemError, + Message = "系统错误,请稍后重试", + Data = null + }); + } + + context.ExceptionHandled = true; + } +} +``` + +### 参数验证 + +```csharp +// 使用 FluentValidation 进行参数验证 +public class CreateBannerRequestValidator : AbstractValidator +{ + public CreateBannerRequestValidator() + { + RuleFor(x => x.ImageUrl) + .NotEmpty().WithMessage("图片URL不能为空"); + + RuleFor(x => x.LinkUrl) + .NotEmpty() + .When(x => x.LinkType == 1 || x.LinkType == 2) + .WithMessage("跳转地址不能为空"); + + RuleFor(x => x.AppId) + .NotEmpty() + .When(x => x.LinkType == 3) + .WithMessage("小程序AppId不能为空"); + + RuleFor(x => x.Status) + .InclusiveBetween(0, 1) + .WithMessage("状态值无效"); + } +} +``` + +## Testing Strategy + +### 测试框架 + +- **单元测试**: xUnit + Moq +- **集成测试**: xUnit + TestServer +- **属性测试**: FsCheck + +### 测试分层 + +1. **单元测试**: 测试 Service 层业务逻辑 +2. **集成测试**: 测试 Controller 层 API 接口 +3. **属性测试**: 验证核心业务规则的正确性 + +### 测试覆盖要求 + +- Service 层方法覆盖率 > 80% +- 核心业务逻辑 100% 覆盖 +- 边界条件和异常情况测试 + + + +## Correctness Properties + +*A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* + +Based on the prework analysis, the following correctness properties have been identified for property-based testing: + +### Property 1: Soft Delete Behavior + +*For any* entity that supports soft delete (Banner, Promotion, AssessmentType, Question, Conclusion, Planner), when a delete operation is performed, the entity's IsDeleted field SHALL be set to 1 and the entity SHALL remain in the database. + +**Validates: Requirements 2.7, 3.6, 4.7, 5.6, 8.5, 12.5** + +### Property 2: Status Update Persistence + +*For any* entity with a Status field and any valid status value, when the status is updated, querying the entity SHALL return the new status value. + +**Validates: Requirements 2.6, 3.5, 4.5, 5.5, 9.4, 12.4, 13.4** + +### Property 3: Pagination Correctness + +*For any* paginated list query with PageIndex and PageSize parameters, the result SHALL contain at most PageSize items and the Total count SHALL equal the actual number of matching records in the database. + +**Validates: Requirements 9.1, 10.1, 13.1, 14.1, 15.1, 16.1** + +### Property 4: Sorting Correctness + +*For any* list query with a Sort field, the returned items SHALL be ordered by the Sort field in the specified direction (ascending or descending). + +**Validates: Requirements 2.1, 3.1, 4.1, 5.1, 8.1, 12.1** + +### Property 5: Required Field Validation + +*For any* create operation with missing required fields, the service SHALL return a validation error and SHALL NOT create the entity. + +**Validates: Requirements 2.2, 3.2, 4.2, 5.2, 6.2, 8.2, 12.2** + +### Property 6: Config Value Validation + +*For any* price configuration update, the value SHALL be a positive decimal number. *For any* commission rate configuration update, the value SHALL be between 0 and 1 (inclusive). + +**Validates: Requirements 1.3, 1.4** + +### Property 7: Banner Link Type Validation + +*For any* Banner with LinkType 1 or 2, the LinkUrl SHALL be non-empty. *For any* Banner with LinkType 3, both LinkUrl and AppId SHALL be non-empty. + +**Validates: Requirements 2.3, 2.4, 2.5** + +### Property 8: Unique Constraint Enforcement + +*For any* AssessmentType, the Code SHALL be unique across all assessment types. *For any* Question within the same AssessmentTypeId, the QuestionNo SHALL be unique. *For any* QuestionCategoryMapping, the QuestionId-CategoryId pair SHALL be unique. + +**Validates: Requirements 4.3, 5.3, 7.4** + +### Property 9: Tree Structure Correctness + +*For any* category tree query, all child categories SHALL have their ParentId referencing an existing parent category, and the tree structure SHALL correctly represent the parent-child relationships. + +**Validates: Requirements 6.1, 6.5** + +### Property 10: Mapping Relationship Bidirectionality + +*For any* QuestionCategoryMapping, querying mappings by QuestionId SHALL include the CategoryId, and querying mappings by CategoryId SHALL include the QuestionId. + +**Validates: Requirements 7.1, 7.2** + +### Property 11: Batch Operation Atomicity + +*For any* batch update of question-category mappings, either all mappings SHALL be updated successfully, or none SHALL be updated (rollback on failure). + +**Validates: Requirements 7.6** + +### Property 12: Refund Status Transitions + +*For any* refund operation, the order Status SHALL transition from 2 (paid) or 3 (completed) to 4 (refunding), and upon completion SHALL transition to 5 (refunded) with RefundTime and RefundAmount recorded. + +**Validates: Requirements 11.1, 11.2, 11.3, 11.4** + +### Property 13: Invite Code Generation Uniqueness + +*For any* batch of generated invite codes, all codes SHALL be unique 5-character uppercase letter strings, and all codes in the same batch SHALL share the same BatchNo. + +**Validates: Requirements 14.3, 14.4** + +### Property 14: Commission Statistics Accuracy + +*For any* commission statistics query, the TotalAmount SHALL equal the sum of all CommissionAmount values, PendingAmount SHALL equal the sum of CommissionAmount where Status is 1, and SettledAmount SHALL equal the sum of CommissionAmount where Status is 2. + +**Validates: Requirements 15.6** + +### Property 15: Withdrawal Status Transitions + +*For any* withdrawal approval, the Status SHALL transition from 1 (pending) to 2 (processing) with AuditUserId and AuditTime recorded. *For any* withdrawal rejection, the Status SHALL transition from 1 to 4 (cancelled) with AuditRemark recorded. *For any* withdrawal completion, the Status SHALL transition from 2 to 3 (completed) with PayTime and PayTransactionId recorded. + +**Validates: Requirements 16.4, 16.5, 16.6, 16.7** + +### Property 16: Authorization Enforcement + +*For any* protected endpoint and any user without the required permission, the system SHALL return HTTP 403 Forbidden. + +**Validates: Requirements 18.2** + +### Property 17: Config Update Timestamp + +*For any* configuration update, the UpdateTime field SHALL be automatically set to the current timestamp. + +**Validates: Requirements 1.7** + +### Property 18: Category Deletion Constraint + +*For any* category with child categories, attempting to delete the category SHALL return an error and the category SHALL remain unchanged. + +**Validates: Requirements 6.6** + diff --git a/.kiro/specs/admin-business-modules/requirements.md b/.kiro/specs/admin-business-modules/requirements.md new file mode 100644 index 0000000..4585bc2 --- /dev/null +++ b/.kiro/specs/admin-business-modules/requirements.md @@ -0,0 +1,263 @@ +# Requirements Document + +## Introduction + +本文档定义了学业邑规划 MiAssessment 管理后台业务模块的需求规格。后台管理系统基于 .NET 10 + SQL Server 2022 技术栈,采用 RPC 风格接口(仅 GET/POST 请求)。系统需要在现有的用户、角色、权限、菜单管理基础上,扩展7个核心业务模块:系统配置管理、内容管理、测评管理、用户管理、订单管理、规划师管理和分销管理。 + +## Glossary + +- **Admin_System**: 后台管理系统,用于管理小程序业务数据 +- **Config_Service**: 系统配置服务,管理系统配置项的增删改查 +- **Content_Service**: 内容管理服务,管理轮播图和宣传图 +- **Assessment_Service**: 测评管理服务,管理测评类型、题库、报告分类和结论 +- **User_Service**: 用户管理服务,管理C端用户信息和等级 +- **Order_Service**: 订单管理服务,管理订单列表、详情和退款 +- **Planner_Service**: 规划师管理服务,管理规划师信息和预约记录 +- **Distribution_Service**: 分销管理服务,管理邀请码、佣金和提现 +- **Operator**: 后台运营人员,具有内容管理、订单管理、用户管理权限 +- **Finance_Staff**: 财务人员,具有订单管理、提现审核权限 +- **Super_Admin**: 超级管理员,具有全部权限 + +## Requirements + +### Requirement 1: 系统配置管理 + +**User Story:** As an Operator, I want to manage system configuration items, so that I can dynamically adjust system parameters like assessment prices, commission rates, and customer service information. + +#### Acceptance Criteria + +1. WHEN an Operator requests the configuration list, THE Config_Service SHALL return all configuration items grouped by ConfigType +2. WHEN an Operator updates a configuration value, THE Config_Service SHALL validate the value format and persist the change +3. WHEN an Operator updates a price configuration, THE Config_Service SHALL validate that the value is a positive decimal number +4. WHEN an Operator updates a commission rate configuration, THE Config_Service SHALL validate that the value is between 0 and 1 +5. IF an Operator attempts to update a configuration with an invalid value, THEN THE Config_Service SHALL return a descriptive error message +6. THE Config_Service SHALL support the following configuration types: price, commission, withdraw, contact, agreement, content +7. WHEN a configuration is updated, THE Config_Service SHALL record the UpdateTime automatically + +### Requirement 2: 轮播图管理 + +**User Story:** As an Operator, I want to manage banner images, so that I can control the promotional content displayed on the mini-program homepage. + +#### Acceptance Criteria + +1. WHEN an Operator requests the banner list, THE Content_Service SHALL return banners sorted by Sort field in descending order +2. WHEN an Operator creates a banner, THE Content_Service SHALL validate that ImageUrl is provided and generate a unique Id +3. WHEN an Operator updates a banner, THE Content_Service SHALL validate the LinkType and corresponding LinkUrl/AppId fields +4. IF LinkType is 1 (internal page) or 2 (external link), THEN THE Content_Service SHALL require LinkUrl to be non-empty +5. IF LinkType is 3 (mini-program), THEN THE Content_Service SHALL require both LinkUrl and AppId to be non-empty +6. WHEN an Operator changes banner status, THE Content_Service SHALL update the Status field (0=disabled, 1=enabled) +7. WHEN an Operator deletes a banner, THE Content_Service SHALL perform soft delete by setting IsDeleted to 1 +8. WHEN an Operator reorders banners, THE Content_Service SHALL update the Sort field for affected banners + +### Requirement 3: 宣传图管理 + +**User Story:** As an Operator, I want to manage promotional images, so that I can control the promotional content displayed at different positions in the mini-program. + +#### Acceptance Criteria + +1. WHEN an Operator requests the promotion list, THE Content_Service SHALL return promotions filtered by Position and sorted by Sort field +2. WHEN an Operator creates a promotion, THE Content_Service SHALL validate that ImageUrl and Position are provided +3. THE Content_Service SHALL support Position values: 1 (homepage bottom), 2 (team page) +4. WHEN an Operator updates a promotion, THE Content_Service SHALL validate the Position value is within allowed range +5. WHEN an Operator changes promotion status, THE Content_Service SHALL update the Status field (0=disabled, 1=enabled) +6. WHEN an Operator deletes a promotion, THE Content_Service SHALL perform soft delete by setting IsDeleted to 1 + +### Requirement 4: 测评类型管理 + +**User Story:** As an Operator, I want to manage assessment types, so that I can configure different assessment products with their prices and content. + +#### Acceptance Criteria + +1. WHEN an Operator requests the assessment type list, THE Assessment_Service SHALL return all assessment types sorted by Sort field +2. WHEN an Operator creates an assessment type, THE Assessment_Service SHALL validate that Name, Code, and Price are provided +3. THE Assessment_Service SHALL ensure Code is unique across all assessment types +4. WHEN an Operator updates an assessment type price, THE Assessment_Service SHALL validate that Price is a positive decimal +5. WHEN an Operator changes assessment type status, THE Assessment_Service SHALL update Status (0=offline, 1=online, 2=coming soon) +6. WHEN an Operator updates IntroContent, THE Assessment_Service SHALL accept rich text or image URL content +7. WHEN an Operator deletes an assessment type, THE Assessment_Service SHALL perform soft delete by setting IsDeleted to 1 + +### Requirement 5: 题库管理 + +**User Story:** As an Operator, I want to manage assessment questions, so that I can configure the question content for each assessment type. + +#### Acceptance Criteria + +1. WHEN an Operator requests the question list, THE Assessment_Service SHALL return questions filtered by AssessmentTypeId and sorted by QuestionNo +2. WHEN an Operator creates a question, THE Assessment_Service SHALL validate that AssessmentTypeId, QuestionNo, and Content are provided +3. THE Assessment_Service SHALL ensure QuestionNo is unique within the same AssessmentTypeId +4. WHEN an Operator updates a question, THE Assessment_Service SHALL validate Content is non-empty +5. WHEN an Operator changes question status, THE Assessment_Service SHALL update Status (0=disabled, 1=enabled) +6. WHEN an Operator deletes a question, THE Assessment_Service SHALL perform soft delete by setting IsDeleted to 1 +7. WHEN an Operator imports questions in batch, THE Assessment_Service SHALL validate all questions and return detailed error information for invalid entries + +### Requirement 6: 报告分类管理 + +**User Story:** As an Operator, I want to manage report categories, so that I can configure the scoring dimensions and hierarchical structure for assessment reports. + +#### Acceptance Criteria + +1. WHEN an Operator requests the category list, THE Assessment_Service SHALL return categories in a hierarchical tree structure +2. WHEN an Operator creates a category, THE Assessment_Service SHALL validate that Name, Code, CategoryType, and ScoreRule are provided +3. THE Assessment_Service SHALL support CategoryType values: 1-8 (八大智能, 个人特质, 细分能力, 先天学习, 学习能力, 大脑类型, 性格类型, 未来能力) +4. THE Assessment_Service SHALL support ScoreRule values: 1 (cumulative 1-10), 2 (binary 0/1) +5. WHEN an Operator creates a sub-category, THE Assessment_Service SHALL validate that ParentId references an existing category +6. WHEN an Operator deletes a category with children, THE Assessment_Service SHALL return an error preventing deletion + +### Requirement 7: 题目分类映射管理 + +**User Story:** As an Operator, I want to manage question-category mappings, so that I can configure which questions contribute to which scoring categories. + +#### Acceptance Criteria + +1. WHEN an Operator requests mappings for a question, THE Assessment_Service SHALL return all associated categories +2. WHEN an Operator requests mappings for a category, THE Assessment_Service SHALL return all associated questions +3. WHEN an Operator creates a mapping, THE Assessment_Service SHALL validate that both QuestionId and CategoryId exist +4. THE Assessment_Service SHALL ensure each QuestionId-CategoryId pair is unique +5. WHEN an Operator deletes a mapping, THE Assessment_Service SHALL remove the association record +6. WHEN an Operator batch updates mappings for a question, THE Assessment_Service SHALL replace all existing mappings atomically + +### Requirement 8: 报告结论管理 + +**User Story:** As an Operator, I want to manage report conclusions, so that I can configure the textual conclusions displayed for different scoring results. + +#### Acceptance Criteria + +1. WHEN an Operator requests conclusions for a category, THE Assessment_Service SHALL return all conclusions sorted by ConclusionType +2. WHEN an Operator creates a conclusion, THE Assessment_Service SHALL validate that CategoryId, ConclusionType, and Content are provided +3. THE Assessment_Service SHALL support ConclusionType values: 1 (strongest), 2 (stronger), 3 (weaker), 4 (weakest) +4. WHEN an Operator updates a conclusion, THE Assessment_Service SHALL accept rich text Content +5. WHEN an Operator deletes a conclusion, THE Assessment_Service SHALL perform soft delete by setting IsDeleted to 1 + +### Requirement 9: C端用户列表管理 + +**User Story:** As an Operator, I want to view and manage C-end user information, so that I can monitor user activities and manage user accounts. + +#### Acceptance Criteria + +1. WHEN an Operator requests the user list, THE User_Service SHALL return users with pagination support +2. THE User_Service SHALL support filtering users by: Uid, Phone, Nickname, UserLevel, Status, CreateTime range +3. WHEN an Operator views user details, THE User_Service SHALL return complete user information including balance and income statistics +4. WHEN an Operator changes user status, THE User_Service SHALL update Status (0=disabled, 1=normal) +5. WHEN an Operator changes user level, THE User_Service SHALL update UserLevel (1=normal, 2=partner, 3=channel partner) +6. THE User_Service SHALL display user's parent user information if ParentUserId exists +7. WHEN an Operator exports user list, THE User_Service SHALL generate a downloadable file with selected fields + +### Requirement 10: 订单列表管理 + +**User Story:** As an Operator, I want to view and manage orders, so that I can monitor transactions and handle customer issues. + +#### Acceptance Criteria + +1. WHEN an Operator requests the order list, THE Order_Service SHALL return orders with pagination support sorted by CreateTime descending +2. THE Order_Service SHALL support filtering orders by: OrderNo, UserId, OrderType, Status, PayType, CreateTime range +3. WHEN an Operator views order details, THE Order_Service SHALL return complete order information including user details and product information +4. THE Order_Service SHALL display OrderType as: 1 (assessment order), 2 (planning order) +5. THE Order_Service SHALL display Status as: 1 (pending payment), 2 (paid), 3 (completed), 4 (refunding), 5 (refunded), 6 (cancelled) +6. WHEN an Operator exports order list, THE Order_Service SHALL generate a downloadable file with selected fields + +### Requirement 11: 退款处理 + +**User Story:** As a Finance_Staff, I want to process refund requests, so that I can handle customer refund applications properly. + +#### Acceptance Criteria + +1. WHEN a Finance_Staff initiates a refund, THE Order_Service SHALL validate that the order Status is 2 (paid) or 3 (completed) +2. WHEN a Finance_Staff initiates a refund, THE Order_Service SHALL update order Status to 4 (refunding) +3. WHEN a refund is completed, THE Order_Service SHALL update order Status to 5 (refunded) and record RefundTime and RefundAmount +4. IF a refund fails, THEN THE Order_Service SHALL revert order Status and record the error reason +5. WHEN processing a refund for an assessment order, THE Order_Service SHALL check if the assessment has been completed +6. THE Order_Service SHALL record RefundReason for audit purposes + +### Requirement 12: 规划师信息管理 + +**User Story:** As an Operator, I want to manage planner information, so that I can configure the planners available for booking in the mini-program. + +#### Acceptance Criteria + +1. WHEN an Operator requests the planner list, THE Planner_Service SHALL return planners sorted by Sort field descending +2. WHEN an Operator creates a planner, THE Planner_Service SHALL validate that Name, Avatar, and Price are provided +3. WHEN an Operator updates a planner, THE Planner_Service SHALL validate that Price is a positive decimal +4. WHEN an Operator changes planner status, THE Planner_Service SHALL update Status (0=disabled, 1=enabled) +5. WHEN an Operator deletes a planner, THE Planner_Service SHALL perform soft delete by setting IsDeleted to 1 +6. WHEN an Operator reorders planners, THE Planner_Service SHALL update the Sort field for affected planners + +### Requirement 13: 预约记录管理 + +**User Story:** As an Operator, I want to view and manage booking records, so that I can track planner appointments and handle scheduling issues. + +#### Acceptance Criteria + +1. WHEN an Operator requests the booking list, THE Planner_Service SHALL return bookings with pagination support +2. THE Planner_Service SHALL support filtering bookings by: PlannerId, UserId, BookingDate range, Status +3. WHEN an Operator views booking details, THE Planner_Service SHALL return complete booking information including user and planner details +4. WHEN an Operator changes booking status, THE Planner_Service SHALL update Status (1=pending, 2=confirmed, 3=completed, 4=cancelled) +5. THE Planner_Service SHALL display student grade information and scores based on Grade value +6. WHEN an Operator exports booking list, THE Planner_Service SHALL generate a downloadable file with selected fields + +### Requirement 14: 邀请码管理 + +**User Story:** As an Operator, I want to manage invite codes, so that I can generate and distribute free assessment codes to partners. + +#### Acceptance Criteria + +1. WHEN an Operator requests the invite code list, THE Distribution_Service SHALL return codes with pagination support +2. THE Distribution_Service SHALL support filtering codes by: Code, BatchNo, AssignUserId, Status +3. WHEN an Operator generates invite codes in batch, THE Distribution_Service SHALL create unique 5-character uppercase letter codes +4. WHEN an Operator generates invite codes, THE Distribution_Service SHALL assign the same BatchNo to all codes in the batch +5. WHEN an Operator assigns codes to a user, THE Distribution_Service SHALL update AssignUserId, AssignTime, and Status to 2 (assigned) +6. THE Distribution_Service SHALL display code usage information: UseUserId, UseOrderId, UseTime when Status is 3 (used) +7. WHEN an Operator exports invite code list, THE Distribution_Service SHALL generate a downloadable file with selected fields + +### Requirement 15: 佣金记录管理 + +**User Story:** As a Finance_Staff, I want to view commission records, so that I can monitor the distribution earnings and verify commission calculations. + +#### Acceptance Criteria + +1. WHEN a Finance_Staff requests the commission list, THE Distribution_Service SHALL return records with pagination support +2. THE Distribution_Service SHALL support filtering commissions by: UserId, FromUserId, OrderId, Level, Status, CreateTime range +3. WHEN a Finance_Staff views commission details, THE Distribution_Service SHALL return complete information including user and order details +4. THE Distribution_Service SHALL display Level as: 1 (direct subordinate), 2 (indirect subordinate) +5. THE Distribution_Service SHALL display Status as: 1 (pending settlement), 2 (settled) +6. THE Distribution_Service SHALL calculate and display commission statistics: total amount, pending amount, settled amount +7. WHEN a Finance_Staff exports commission list, THE Distribution_Service SHALL generate a downloadable file with selected fields + +### Requirement 16: 提现审核管理 + +**User Story:** As a Finance_Staff, I want to review and process withdrawal requests, so that I can approve or reject user withdrawal applications. + +#### Acceptance Criteria + +1. WHEN a Finance_Staff requests the withdrawal list, THE Distribution_Service SHALL return records with pagination support sorted by CreateTime descending +2. THE Distribution_Service SHALL support filtering withdrawals by: WithdrawalNo, UserId, Status, CreateTime range +3. WHEN a Finance_Staff views withdrawal details, THE Distribution_Service SHALL return complete information including user balance history +4. WHEN a Finance_Staff approves a withdrawal, THE Distribution_Service SHALL update Status to 2 (processing) and record AuditUserId and AuditTime +5. WHEN a Finance_Staff rejects a withdrawal, THE Distribution_Service SHALL update Status to 4 (cancelled) and record AuditRemark +6. WHEN a withdrawal payment is completed, THE Distribution_Service SHALL update Status to 3 (completed) and record PayTime and PayTransactionId +7. IF a withdrawal payment fails, THEN THE Distribution_Service SHALL revert user balance and record the error reason +8. THE Distribution_Service SHALL display Status as: 1 (pending), 2 (processing), 3 (completed), 4 (cancelled) + +### Requirement 17: 数据统计仪表盘 + +**User Story:** As an Operator, I want to view business statistics on a dashboard, so that I can monitor key business metrics at a glance. + +#### Acceptance Criteria + +1. WHEN an Operator accesses the dashboard, THE Admin_System SHALL display today's statistics: new users, new orders, revenue +2. THE Admin_System SHALL display cumulative statistics: total users, total orders, total revenue +3. THE Admin_System SHALL display recent trends: user growth chart, order trend chart, revenue trend chart +4. THE Admin_System SHALL support date range selection for trend charts +5. THE Admin_System SHALL display pending items: pending withdrawals count, pending bookings count +6. WHEN statistics data is requested, THE Admin_System SHALL return cached data if available and refresh in background + +### Requirement 18: 权限控制 + +**User Story:** As a Super_Admin, I want to control access to business modules, so that different roles can only access their authorized functions. + +#### Acceptance Criteria + +1. THE Admin_System SHALL integrate with the existing role-permission system +2. WHEN a user without permission accesses a protected endpoint, THE Admin_System SHALL return 403 Forbidden +3. THE Admin_System SHALL define business permissions for each module: config, content, assessment, user, order, planner, distribution +4. THE Admin_System SHALL support permission granularity: view, create, update, delete for each module +5. WHEN a new business module is added, THE Admin_System SHALL register its permissions in the permission system diff --git a/.kiro/specs/admin-business-modules/tasks.md b/.kiro/specs/admin-business-modules/tasks.md new file mode 100644 index 0000000..03cda5f --- /dev/null +++ b/.kiro/specs/admin-business-modules/tasks.md @@ -0,0 +1,306 @@ +# Implementation Plan: Admin Business Modules + +## Overview + +本实现计划将后台管理系统业务模块分解为可执行的开发任务。基于 .NET 10 + SQL Server 2022 技术栈,在现有 MiAssessment.Admin.Business 项目基础上扩展7个核心业务模块。 + +## Tasks + +- [ ] 1. 项目基础设施搭建 + - [ ] 1.1 创建业务实体类 (Entities) + - 在 MiAssessment.Admin.Business/Entities 目录下创建所有业务实体类 + - 包括: Config, Banner, Promotion, AssessmentType, Question, ReportCategory, QuestionCategoryMapping, ReportConclusion, User, Order, Planner, PlannerBooking, InviteCode, Commission, Withdrawal + - 每个实体类映射到对应的数据库表 + - _Requirements: 1.1-1.7, 2.1-2.8, 3.1-3.6, 4.1-4.7, 5.1-5.7, 6.1-6.6, 7.1-7.6, 8.1-8.5, 9.1-9.7, 10.1-10.6, 11.1-11.6, 12.1-12.6, 13.1-13.6, 14.1-14.7, 15.1-15.7, 16.1-16.8_ + + - [ ] 1.2 扩展 AdminBusinessDbContext + - 在 AdminBusinessDbContext 中添加所有业务实体的 DbSet + - 配置实体映射和索引 + - _Requirements: 所有模块_ + + - [ ] 1.3 创建通用模型和基础类 + - 创建 PagedRequest, PagedResult, ApiResponse, UpdateStatusRequest, UpdateSortRequest 等通用模型 + - 创建 ErrorCodes 错误码常量类 + - 创建 BusinessException 业务异常类 + - _Requirements: 所有模块_ + +- [ ] 2. 系统配置管理模块 + - [ ] 2.1 实现 ConfigService + - 创建 IConfigService 接口和 ConfigService 实现 + - 实现 GetConfigListAsync, GetConfigByKeyAsync, UpdateConfigAsync, ValidateConfigValueAsync 方法 + - 实现配置值验证逻辑(价格、佣金比例) + - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.7_ + + - [ ] 2.2 编写 ConfigService 属性测试 + - **Property 6: Config Value Validation** + - **Property 17: Config Update Timestamp** + - **Validates: Requirements 1.3, 1.4, 1.7** + + - [ ] 2.3 实现 ConfigController + - 创建 ConfigController 控制器 + - 实现 GetList, Update, GetByKey 接口 + - _Requirements: 1.1-1.7_ + +- [ ] 3. 内容管理模块 - 轮播图 + - [ ] 3.1 实现 ContentService - Banner 部分 + - 创建 IContentService 接口和 ContentService 实现 + - 实现 Banner 相关方法: GetBannerListAsync, CreateBannerAsync, UpdateBannerAsync, DeleteBannerAsync, UpdateBannerStatusAsync, UpdateBannerSortAsync + - 实现 LinkType 验证逻辑 + - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8_ + + - [ ] 3.2 编写 Banner 属性测试 + - **Property 1: Soft Delete Behavior** + - **Property 4: Sorting Correctness** + - **Property 7: Banner Link Type Validation** + - **Validates: Requirements 2.1, 2.3, 2.4, 2.5, 2.7** + + - [ ] 3.3 实现 ContentController - Banner 部分 + - 创建 ContentController 控制器 + - 实现 Banner 相关接口: banner/getList, banner/create, banner/update, banner/delete, banner/updateStatus, banner/updateSort + - _Requirements: 2.1-2.8_ + +- [ ] 4. 内容管理模块 - 宣传图 + - [ ] 4.1 实现 ContentService - Promotion 部分 + - 扩展 ContentService 实现 Promotion 相关方法 + - 实现 GetPromotionListAsync, CreatePromotionAsync, UpdatePromotionAsync, DeletePromotionAsync, UpdatePromotionStatusAsync + - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6_ + + - [ ] 4.2 编写 Promotion 属性测试 + - **Property 2: Status Update Persistence** + - **Validates: Requirements 3.5** + + - [ ] 4.3 实现 ContentController - Promotion 部分 + - 扩展 ContentController 实现 Promotion 相关接口 + - 实现 promotion/getList, promotion/create, promotion/update, promotion/delete, promotion/updateStatus + - _Requirements: 3.1-3.6_ + +- [ ] 5. Checkpoint - 内容管理模块完成 + - Ensure all tests pass, ask the user if questions arise. + +- [ ] 6. 测评管理模块 - 测评类型 + - [ ] 6.1 实现 AssessmentService - Type 部分 + - 创建 IAssessmentService 接口和 AssessmentService 实现 + - 实现 GetTypeListAsync, CreateTypeAsync, UpdateTypeAsync, DeleteTypeAsync + - 实现 Code 唯一性验证 + - _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7_ + + - [ ] 6.2 编写 AssessmentType 属性测试 + - **Property 8: Unique Constraint Enforcement (Code)** + - **Validates: Requirements 4.3** + + - [ ] 6.3 实现 AssessmentController - Type 部分 + - 创建 AssessmentController 控制器 + - 实现 type/getList, type/create, type/update, type/delete 接口 + - _Requirements: 4.1-4.7_ + +- [ ] 7. 测评管理模块 - 题库 + - [ ] 7.1 实现 AssessmentService - Question 部分 + - 扩展 AssessmentService 实现 Question 相关方法 + - 实现 GetQuestionListAsync, CreateQuestionAsync, UpdateQuestionAsync, DeleteQuestionAsync, BatchImportQuestionsAsync + - 实现 QuestionNo 唯一性验证(同一 AssessmentTypeId 内) + - _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7_ + + - [ ] 7.2 编写 Question 属性测试 + - **Property 5: Required Field Validation** + - **Property 8: Unique Constraint Enforcement (QuestionNo)** + - **Validates: Requirements 5.2, 5.3** + + - [ ] 7.3 实现 AssessmentController - Question 部分 + - 扩展 AssessmentController 实现 Question 相关接口 + - 实现 question/getList, question/create, question/update, question/delete, question/batchImport + - _Requirements: 5.1-5.7_ + +- [ ] 8. 测评管理模块 - 报告分类 + - [ ] 8.1 实现 AssessmentService - Category 部分 + - 扩展 AssessmentService 实现 Category 相关方法 + - 实现 GetCategoryTreeAsync, CreateCategoryAsync, UpdateCategoryAsync, DeleteCategoryAsync + - 实现树形结构构建和删除约束检查 + - _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 6.6_ + + - [ ] 8.2 编写 Category 属性测试 + - **Property 9: Tree Structure Correctness** + - **Property 18: Category Deletion Constraint** + - **Validates: Requirements 6.1, 6.5, 6.6** + + - [ ] 8.3 实现 AssessmentController - Category 部分 + - 扩展 AssessmentController 实现 Category 相关接口 + - 实现 category/getTree, category/create, category/update, category/delete + - _Requirements: 6.1-6.6_ + +- [ ] 9. 测评管理模块 - 题目分类映射 + - [ ] 9.1 实现 AssessmentService - Mapping 部分 + - 扩展 AssessmentService 实现 Mapping 相关方法 + - 实现 GetMappingsByQuestionAsync, GetMappingsByCategoryAsync, BatchUpdateMappingsAsync + - 实现原子性批量更新 + - _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6_ + + - [ ] 9.2 编写 Mapping 属性测试 + - **Property 10: Mapping Relationship Bidirectionality** + - **Property 11: Batch Operation Atomicity** + - **Validates: Requirements 7.1, 7.2, 7.6** + + - [ ] 9.3 实现 AssessmentController - Mapping 部分 + - 扩展 AssessmentController 实现 Mapping 相关接口 + - 实现 mapping/getByQuestion, mapping/getByCategory, mapping/batchUpdate + - _Requirements: 7.1-7.6_ + +- [ ] 10. 测评管理模块 - 报告结论 + - [ ] 10.1 实现 AssessmentService - Conclusion 部分 + - 扩展 AssessmentService 实现 Conclusion 相关方法 + - 实现 GetConclusionListAsync, CreateConclusionAsync, UpdateConclusionAsync, DeleteConclusionAsync + - _Requirements: 8.1, 8.2, 8.3, 8.4, 8.5_ + + - [ ] 10.2 实现 AssessmentController - Conclusion 部分 + - 扩展 AssessmentController 实现 Conclusion 相关接口 + - 实现 conclusion/getList, conclusion/create, conclusion/update, conclusion/delete + - _Requirements: 8.1-8.5_ + +- [ ] 11. Checkpoint - 测评管理模块完成 + - Ensure all tests pass, ask the user if questions arise. + +- [ ] 12. 用户管理模块 + - [ ] 12.1 实现 UserBusinessService + - 扩展现有 UserBusinessService + - 实现 GetUserListAsync, GetUserDetailAsync, UpdateUserStatusAsync, UpdateUserLevelAsync, ExportUsersAsync + - 实现多条件筛选和分页 + - _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7_ + + - [ ] 12.2 编写 User 属性测试 + - **Property 3: Pagination Correctness** + - **Validates: Requirements 9.1** + + - [ ] 12.3 实现 UserController + - 扩展现有 UserController + - 实现 getList, getDetail, updateStatus, updateLevel, export 接口 + - _Requirements: 9.1-9.7_ + +- [ ] 13. 订单管理模块 + - [ ] 13.1 实现 OrderService + - 创建 IOrderService 接口和 OrderService 实现 + - 实现 GetOrderListAsync, GetOrderDetailAsync, RefundAsync + - 实现退款状态流转和验证逻辑 + - _Requirements: 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6_ + + - [ ] 13.2 编写 Order 属性测试 + - **Property 12: Refund Status Transitions** + - **Validates: Requirements 11.1, 11.2, 11.3, 11.4** + + - [ ] 13.3 实现 OrderController + - 创建 OrderController 控制器 + - 实现 getList, getDetail, refund, export 接口 + - _Requirements: 10.1-10.6, 11.1-11.6_ + +- [ ] 14. Checkpoint - 用户和订单模块完成 + - Ensure all tests pass, ask the user if questions arise. + +- [ ] 15. 规划师管理模块 + - [ ] 15.1 实现 PlannerService + - 创建 IPlannerService 接口和 PlannerService 实现 + - 实现 Planner 相关方法: GetPlannerListAsync, CreatePlannerAsync, UpdatePlannerAsync, DeletePlannerAsync, UpdatePlannerStatusAsync, UpdatePlannerSortAsync + - 实现 Booking 相关方法: GetBookingListAsync, GetBookingDetailAsync, UpdateBookingStatusAsync + - _Requirements: 12.1, 12.2, 12.3, 12.4, 12.5, 12.6, 13.1, 13.2, 13.3, 13.4, 13.5, 13.6_ + + - [ ] 15.2 实现 PlannerController + - 创建 PlannerController 控制器 + - 实现 Planner 接口: getList, create, update, delete, updateStatus, updateSort + - 实现 Booking 接口: booking/getList, booking/getDetail, booking/updateStatus, booking/export + - _Requirements: 12.1-12.6, 13.1-13.6_ + +- [ ] 16. 分销管理模块 - 邀请码 + - [ ] 16.1 实现 DistributionService - InviteCode 部分 + - 创建 IDistributionService 接口和 DistributionService 实现 + - 实现 GetInviteCodeListAsync, GenerateInviteCodesAsync, AssignInviteCodesAsync + - 实现唯一邀请码生成算法(5位大写字母) + - _Requirements: 14.1, 14.2, 14.3, 14.4, 14.5, 14.6, 14.7_ + + - [ ] 16.2 编写 InviteCode 属性测试 + - **Property 13: Invite Code Generation Uniqueness** + - **Validates: Requirements 14.3, 14.4** + + - [ ] 16.3 实现 DistributionController - InviteCode 部分 + - 创建 DistributionController 控制器 + - 实现 inviteCode/getList, inviteCode/generate, inviteCode/assign, inviteCode/export + - _Requirements: 14.1-14.7_ + +- [ ] 17. 分销管理模块 - 佣金记录 + - [ ] 17.1 实现 DistributionService - Commission 部分 + - 扩展 DistributionService 实现 Commission 相关方法 + - 实现 GetCommissionListAsync, GetCommissionDetailAsync, GetCommissionStatisticsAsync + - 实现佣金统计计算 + - _Requirements: 15.1, 15.2, 15.3, 15.4, 15.5, 15.6, 15.7_ + + - [ ] 17.2 编写 Commission 属性测试 + - **Property 14: Commission Statistics Accuracy** + - **Validates: Requirements 15.6** + + - [ ] 17.3 实现 DistributionController - Commission 部分 + - 扩展 DistributionController 实现 Commission 相关接口 + - 实现 commission/getList, commission/getDetail, commission/getStatistics, commission/export + - _Requirements: 15.1-15.7_ + +- [ ] 18. 分销管理模块 - 提现审核 + - [ ] 18.1 实现 DistributionService - Withdrawal 部分 + - 扩展 DistributionService 实现 Withdrawal 相关方法 + - 实现 GetWithdrawalListAsync, GetWithdrawalDetailAsync, ApproveWithdrawalAsync, RejectWithdrawalAsync, CompleteWithdrawalAsync + - 实现提现状态流转和余额回滚逻辑 + - _Requirements: 16.1, 16.2, 16.3, 16.4, 16.5, 16.6, 16.7, 16.8_ + + - [ ] 18.2 编写 Withdrawal 属性测试 + - **Property 15: Withdrawal Status Transitions** + - **Validates: Requirements 16.4, 16.5, 16.6, 16.7** + + - [ ] 18.3 实现 DistributionController - Withdrawal 部分 + - 扩展 DistributionController 实现 Withdrawal 相关接口 + - 实现 withdrawal/getList, withdrawal/getDetail, withdrawal/approve, withdrawal/reject, withdrawal/complete, withdrawal/export + - _Requirements: 16.1-16.8_ + +- [ ] 19. Checkpoint - 分销管理模块完成 + - Ensure all tests pass, ask the user if questions arise. + +- [ ] 20. 数据统计仪表盘模块 + - [ ] 20.1 实现 DashboardService + - 扩展现有 DashboardService + - 实现 GetOverviewAsync, GetTrendsAsync, GetPendingItemsAsync + - 实现统计数据计算和趋势数据生成 + - _Requirements: 17.1, 17.2, 17.3, 17.4, 17.5_ + + - [ ] 20.2 实现 DashboardController + - 扩展现有 DashboardController + - 实现 getOverview, getTrends, getPendingItems 接口 + - _Requirements: 17.1-17.5_ + +- [ ] 21. 权限控制集成 + - [ ] 21.1 定义业务模块权限 + - 在权限系统中注册业务模块权限 + - 定义权限: config, content, assessment, user, order, planner, distribution + - 定义权限粒度: view, create, update, delete + - _Requirements: 18.3, 18.4, 18.5_ + + - [ ] 21.2 编写权限控制属性测试 + - **Property 16: Authorization Enforcement** + - **Validates: Requirements 18.2** + + - [ ] 21.3 应用权限控制到所有控制器 + - 在所有业务控制器方法上添加 [RequirePermission] 特性 + - 配置权限检查中间件 + - _Requirements: 18.1, 18.2_ + +- [ ] 22. 服务注册和依赖注入配置 + - [ ] 22.1 配置服务注册 + - 在 ServiceCollectionExtensions 中注册所有业务服务 + - 配置 AutoMapper 映射 + - 配置 FluentValidation 验证器 + - _Requirements: 所有模块_ + +- [ ] 23. Final Checkpoint - 全部模块完成 + - Ensure all tests pass, ask the user if questions arise. + +## Notes + +- All tasks are required for comprehensive testing +- Each task references specific requirements for traceability +- Checkpoints ensure incremental validation +- Property tests validate universal correctness properties +- Unit tests validate specific examples and edge cases +- 所有接口采用 RPC 风格,仅使用 GET 和 POST 请求 +- 实体类需要与现有数据库表结构保持一致