1118 lines
34 KiB
Markdown
1118 lines
34 KiB
Markdown
# 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<ApiResponse<List<ConfigGroupDto>>> GetList();
|
|
|
|
// POST /api/admin/config/update - 更新配置
|
|
[HttpPost("update")]
|
|
Task<ApiResponse<bool>> Update(UpdateConfigRequest request);
|
|
|
|
// GET /api/admin/config/getByKey - 根据Key获取配置
|
|
[HttpGet("getByKey")]
|
|
Task<ApiResponse<ConfigDto>> GetByKey(string configKey);
|
|
}
|
|
```
|
|
|
|
#### Service Interface: IConfigService
|
|
|
|
```csharp
|
|
public interface IConfigService
|
|
{
|
|
Task<List<ConfigGroupDto>> GetConfigListAsync();
|
|
Task<ConfigDto> GetConfigByKeyAsync(string configKey);
|
|
Task<bool> UpdateConfigAsync(string configKey, string configValue);
|
|
Task<bool> 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<ApiResponse<PagedResult<BannerDto>>> GetBannerList(BannerQueryRequest request);
|
|
|
|
[HttpPost("banner/create")]
|
|
Task<ApiResponse<long>> CreateBanner(CreateBannerRequest request);
|
|
|
|
[HttpPost("banner/update")]
|
|
Task<ApiResponse<bool>> UpdateBanner(UpdateBannerRequest request);
|
|
|
|
[HttpPost("banner/delete")]
|
|
Task<ApiResponse<bool>> DeleteBanner(long id);
|
|
|
|
[HttpPost("banner/updateStatus")]
|
|
Task<ApiResponse<bool>> UpdateBannerStatus(UpdateStatusRequest request);
|
|
|
|
[HttpPost("banner/updateSort")]
|
|
Task<ApiResponse<bool>> UpdateBannerSort(UpdateSortRequest request);
|
|
|
|
// Promotion APIs
|
|
[HttpGet("promotion/getList")]
|
|
Task<ApiResponse<PagedResult<PromotionDto>>> GetPromotionList(PromotionQueryRequest request);
|
|
|
|
[HttpPost("promotion/create")]
|
|
Task<ApiResponse<long>> CreatePromotion(CreatePromotionRequest request);
|
|
|
|
[HttpPost("promotion/update")]
|
|
Task<ApiResponse<bool>> UpdatePromotion(UpdatePromotionRequest request);
|
|
|
|
[HttpPost("promotion/delete")]
|
|
Task<ApiResponse<bool>> DeletePromotion(long id);
|
|
|
|
[HttpPost("promotion/updateStatus")]
|
|
Task<ApiResponse<bool>> UpdatePromotionStatus(UpdateStatusRequest request);
|
|
}
|
|
```
|
|
|
|
#### Service Interface: IContentService
|
|
|
|
```csharp
|
|
public interface IContentService
|
|
{
|
|
// Banner operations
|
|
Task<PagedResult<BannerDto>> GetBannerListAsync(BannerQueryRequest request);
|
|
Task<BannerDto> GetBannerByIdAsync(long id);
|
|
Task<long> CreateBannerAsync(CreateBannerRequest request);
|
|
Task<bool> UpdateBannerAsync(UpdateBannerRequest request);
|
|
Task<bool> DeleteBannerAsync(long id);
|
|
Task<bool> UpdateBannerStatusAsync(long id, int status);
|
|
Task<bool> UpdateBannerSortAsync(List<SortItem> items);
|
|
|
|
// Promotion operations
|
|
Task<PagedResult<PromotionDto>> GetPromotionListAsync(PromotionQueryRequest request);
|
|
Task<PromotionDto> GetPromotionByIdAsync(long id);
|
|
Task<long> CreatePromotionAsync(CreatePromotionRequest request);
|
|
Task<bool> UpdatePromotionAsync(UpdatePromotionRequest request);
|
|
Task<bool> DeletePromotionAsync(long id);
|
|
Task<bool> 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<ApiResponse<PagedResult<AssessmentTypeDto>>> GetTypeList(AssessmentTypeQueryRequest request);
|
|
|
|
[HttpPost("type/create")]
|
|
Task<ApiResponse<long>> CreateType(CreateAssessmentTypeRequest request);
|
|
|
|
[HttpPost("type/update")]
|
|
Task<ApiResponse<bool>> UpdateType(UpdateAssessmentTypeRequest request);
|
|
|
|
[HttpPost("type/delete")]
|
|
Task<ApiResponse<bool>> DeleteType(long id);
|
|
|
|
// Question APIs
|
|
[HttpGet("question/getList")]
|
|
Task<ApiResponse<PagedResult<QuestionDto>>> GetQuestionList(QuestionQueryRequest request);
|
|
|
|
[HttpPost("question/create")]
|
|
Task<ApiResponse<long>> CreateQuestion(CreateQuestionRequest request);
|
|
|
|
[HttpPost("question/update")]
|
|
Task<ApiResponse<bool>> UpdateQuestion(UpdateQuestionRequest request);
|
|
|
|
[HttpPost("question/delete")]
|
|
Task<ApiResponse<bool>> DeleteQuestion(long id);
|
|
|
|
[HttpPost("question/batchImport")]
|
|
Task<ApiResponse<BatchImportResult>> BatchImportQuestions(BatchImportQuestionsRequest request);
|
|
|
|
// Report Category APIs
|
|
[HttpGet("category/getTree")]
|
|
Task<ApiResponse<List<CategoryTreeNode>>> GetCategoryTree(long assessmentTypeId);
|
|
|
|
[HttpPost("category/create")]
|
|
Task<ApiResponse<long>> CreateCategory(CreateCategoryRequest request);
|
|
|
|
[HttpPost("category/update")]
|
|
Task<ApiResponse<bool>> UpdateCategory(UpdateCategoryRequest request);
|
|
|
|
[HttpPost("category/delete")]
|
|
Task<ApiResponse<bool>> DeleteCategory(long id);
|
|
|
|
// Question-Category Mapping APIs
|
|
[HttpGet("mapping/getByQuestion")]
|
|
Task<ApiResponse<List<CategoryDto>>> GetMappingsByQuestion(long questionId);
|
|
|
|
[HttpGet("mapping/getByCategory")]
|
|
Task<ApiResponse<List<QuestionDto>>> GetMappingsByCategory(long categoryId);
|
|
|
|
[HttpPost("mapping/batchUpdate")]
|
|
Task<ApiResponse<bool>> BatchUpdateMappings(BatchUpdateMappingsRequest request);
|
|
|
|
// Report Conclusion APIs
|
|
[HttpGet("conclusion/getList")]
|
|
Task<ApiResponse<List<ConclusionDto>>> GetConclusionList(long categoryId);
|
|
|
|
[HttpPost("conclusion/create")]
|
|
Task<ApiResponse<long>> CreateConclusion(CreateConclusionRequest request);
|
|
|
|
[HttpPost("conclusion/update")]
|
|
Task<ApiResponse<bool>> UpdateConclusion(UpdateConclusionRequest request);
|
|
|
|
[HttpPost("conclusion/delete")]
|
|
Task<ApiResponse<bool>> DeleteConclusion(long id);
|
|
}
|
|
```
|
|
|
|
### 4. 用户管理模块 (User Module)
|
|
|
|
#### Controller: UserController
|
|
|
|
```csharp
|
|
[Route("api/admin/user")]
|
|
public class UserController : BusinessControllerBase
|
|
{
|
|
[HttpGet("getList")]
|
|
Task<ApiResponse<PagedResult<UserDto>>> GetList(UserQueryRequest request);
|
|
|
|
[HttpGet("getDetail")]
|
|
Task<ApiResponse<UserDetailDto>> GetDetail(long id);
|
|
|
|
[HttpPost("updateStatus")]
|
|
Task<ApiResponse<bool>> UpdateStatus(UpdateUserStatusRequest request);
|
|
|
|
[HttpPost("updateLevel")]
|
|
Task<ApiResponse<bool>> UpdateLevel(UpdateUserLevelRequest request);
|
|
|
|
[HttpGet("export")]
|
|
Task<IActionResult> Export(UserQueryRequest request);
|
|
}
|
|
```
|
|
|
|
### 5. 订单管理模块 (Order Module)
|
|
|
|
#### Controller: OrderController
|
|
|
|
```csharp
|
|
[Route("api/admin/order")]
|
|
public class OrderController : BusinessControllerBase
|
|
{
|
|
[HttpGet("getList")]
|
|
Task<ApiResponse<PagedResult<OrderDto>>> GetList(OrderQueryRequest request);
|
|
|
|
[HttpGet("getDetail")]
|
|
Task<ApiResponse<OrderDetailDto>> GetDetail(long id);
|
|
|
|
[HttpPost("refund")]
|
|
Task<ApiResponse<bool>> Refund(RefundRequest request);
|
|
|
|
[HttpGet("export")]
|
|
Task<IActionResult> Export(OrderQueryRequest request);
|
|
}
|
|
```
|
|
|
|
### 6. 规划师管理模块 (Planner Module)
|
|
|
|
#### Controller: PlannerController
|
|
|
|
```csharp
|
|
[Route("api/admin/planner")]
|
|
public class PlannerController : BusinessControllerBase
|
|
{
|
|
// Planner APIs
|
|
[HttpGet("getList")]
|
|
Task<ApiResponse<PagedResult<PlannerDto>>> GetList(PlannerQueryRequest request);
|
|
|
|
[HttpPost("create")]
|
|
Task<ApiResponse<long>> Create(CreatePlannerRequest request);
|
|
|
|
[HttpPost("update")]
|
|
Task<ApiResponse<bool>> Update(UpdatePlannerRequest request);
|
|
|
|
[HttpPost("delete")]
|
|
Task<ApiResponse<bool>> Delete(long id);
|
|
|
|
[HttpPost("updateStatus")]
|
|
Task<ApiResponse<bool>> UpdateStatus(UpdateStatusRequest request);
|
|
|
|
[HttpPost("updateSort")]
|
|
Task<ApiResponse<bool>> UpdateSort(UpdateSortRequest request);
|
|
|
|
// Booking APIs
|
|
[HttpGet("booking/getList")]
|
|
Task<ApiResponse<PagedResult<BookingDto>>> GetBookingList(BookingQueryRequest request);
|
|
|
|
[HttpGet("booking/getDetail")]
|
|
Task<ApiResponse<BookingDetailDto>> GetBookingDetail(long id);
|
|
|
|
[HttpPost("booking/updateStatus")]
|
|
Task<ApiResponse<bool>> UpdateBookingStatus(UpdateBookingStatusRequest request);
|
|
|
|
[HttpGet("booking/export")]
|
|
Task<IActionResult> ExportBookings(BookingQueryRequest request);
|
|
}
|
|
```
|
|
|
|
### 7. 分销管理模块 (Distribution Module)
|
|
|
|
#### Controller: DistributionController
|
|
|
|
```csharp
|
|
[Route("api/admin/distribution")]
|
|
public class DistributionController : BusinessControllerBase
|
|
{
|
|
// Invite Code APIs
|
|
[HttpGet("inviteCode/getList")]
|
|
Task<ApiResponse<PagedResult<InviteCodeDto>>> GetInviteCodeList(InviteCodeQueryRequest request);
|
|
|
|
[HttpPost("inviteCode/generate")]
|
|
Task<ApiResponse<GenerateResult>> GenerateInviteCodes(GenerateInviteCodesRequest request);
|
|
|
|
[HttpPost("inviteCode/assign")]
|
|
Task<ApiResponse<bool>> AssignInviteCodes(AssignInviteCodesRequest request);
|
|
|
|
[HttpGet("inviteCode/export")]
|
|
Task<IActionResult> ExportInviteCodes(InviteCodeQueryRequest request);
|
|
|
|
// Commission APIs
|
|
[HttpGet("commission/getList")]
|
|
Task<ApiResponse<PagedResult<CommissionDto>>> GetCommissionList(CommissionQueryRequest request);
|
|
|
|
[HttpGet("commission/getDetail")]
|
|
Task<ApiResponse<CommissionDetailDto>> GetCommissionDetail(long id);
|
|
|
|
[HttpGet("commission/getStatistics")]
|
|
Task<ApiResponse<CommissionStatisticsDto>> GetCommissionStatistics(CommissionStatisticsRequest request);
|
|
|
|
[HttpGet("commission/export")]
|
|
Task<IActionResult> ExportCommissions(CommissionQueryRequest request);
|
|
|
|
// Withdrawal APIs
|
|
[HttpGet("withdrawal/getList")]
|
|
Task<ApiResponse<PagedResult<WithdrawalDto>>> GetWithdrawalList(WithdrawalQueryRequest request);
|
|
|
|
[HttpGet("withdrawal/getDetail")]
|
|
Task<ApiResponse<WithdrawalDetailDto>> GetWithdrawalDetail(long id);
|
|
|
|
[HttpPost("withdrawal/approve")]
|
|
Task<ApiResponse<bool>> ApproveWithdrawal(ApproveWithdrawalRequest request);
|
|
|
|
[HttpPost("withdrawal/reject")]
|
|
Task<ApiResponse<bool>> RejectWithdrawal(RejectWithdrawalRequest request);
|
|
|
|
[HttpPost("withdrawal/complete")]
|
|
Task<ApiResponse<bool>> CompleteWithdrawal(CompleteWithdrawalRequest request);
|
|
|
|
[HttpGet("withdrawal/export")]
|
|
Task<IActionResult> ExportWithdrawals(WithdrawalQueryRequest request);
|
|
}
|
|
```
|
|
|
|
### 8. 数据统计模块 (Dashboard Module)
|
|
|
|
#### Controller: DashboardController
|
|
|
|
```csharp
|
|
[Route("api/admin/dashboard")]
|
|
public class DashboardController : BusinessControllerBase
|
|
{
|
|
[HttpGet("getOverview")]
|
|
Task<ApiResponse<DashboardOverviewDto>> GetOverview();
|
|
|
|
[HttpGet("getTrends")]
|
|
Task<ApiResponse<TrendsDto>> GetTrends(TrendsQueryRequest request);
|
|
|
|
[HttpGet("getPendingItems")]
|
|
Task<ApiResponse<PendingItemsDto>> GetPendingItems();
|
|
}
|
|
```
|
|
|
|
## Data Models
|
|
|
|
### 通用模型
|
|
|
|
```csharp
|
|
// 分页请求基类
|
|
public class PagedRequest
|
|
{
|
|
public int PageIndex { get; set; } = 1;
|
|
public int PageSize { get; set; } = 20;
|
|
}
|
|
|
|
// 分页结果
|
|
public class PagedResult<T>
|
|
{
|
|
public List<T> 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<T>
|
|
{
|
|
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<SortItem> 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<ConfigDto> 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<long> 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<CategoryTreeNode> 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<string> 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<TrendItem> UserTrend { get; set; }
|
|
public List<TrendItem> OrderTrend { get; set; }
|
|
public List<TrendItem> 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<object>
|
|
{
|
|
Code = bizEx.Code,
|
|
Message = bizEx.Message,
|
|
Data = null
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// 记录日志
|
|
_logger.LogError(context.Exception, "Unhandled exception");
|
|
|
|
context.Result = new JsonResult(new ApiResponse<object>
|
|
{
|
|
Code = ErrorCodes.SystemError,
|
|
Message = "系统错误,请稍后重试",
|
|
Data = null
|
|
});
|
|
}
|
|
|
|
context.ExceptionHandled = true;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 参数验证
|
|
|
|
```csharp
|
|
// 使用 FluentValidation 进行参数验证
|
|
public class CreateBannerRequestValidator : AbstractValidator<CreateBannerRequest>
|
|
{
|
|
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**
|
|
|