- 创建 miniapp-api spec (requirements, design, tasks) - 添加 API开发任务清单,包含页面-API对照验证 - 规划28个待开发接口,按P0/P1/P2优先级排列
1200 lines
32 KiB
Markdown
1200 lines
32 KiB
Markdown
# Design Document: 小程序API开发
|
||
|
||
## Overview
|
||
|
||
本设计文档描述学业邑规划(MiAssessment)微信小程序后端API的技术实现方案。系统采用分层架构,遵循.NET Web API最佳实践,实现28个API接口,覆盖8个业务模块。
|
||
|
||
### 技术栈
|
||
- 后端框架:.NET 10 Web API (C#)
|
||
- 数据库:SQL Server 2022
|
||
- ORM:Entity Framework Core
|
||
- 缓存:Redis
|
||
- 接口风格:RPC风格(仅GET/POST请求)
|
||
|
||
### 模块优先级
|
||
- P0(核心):首页模块、测评模块、订单模块
|
||
- P1(重要):业务详情模块、规划师模块、分销模块
|
||
- P2(辅助):系统模块、团队模块
|
||
|
||
## Architecture
|
||
|
||
### 系统架构图
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "小程序端"
|
||
MP[微信小程序]
|
||
end
|
||
|
||
subgraph "API层"
|
||
HC[HomeController]
|
||
BC[BusinessController]
|
||
AC[AssessmentController]
|
||
OC[OrderController]
|
||
PC[PlannerController]
|
||
IC[InviteController]
|
||
SC[SystemController]
|
||
TC[TeamController]
|
||
end
|
||
|
||
subgraph "服务层"
|
||
HS[HomeService]
|
||
BS[BusinessService]
|
||
AS[AssessmentService]
|
||
OS[OrderService]
|
||
PS[PlannerService]
|
||
IS[InviteService]
|
||
SS[SystemService]
|
||
TS[TeamService]
|
||
end
|
||
|
||
subgraph "数据层"
|
||
DB[(SQL Server)]
|
||
Redis[(Redis Cache)]
|
||
end
|
||
|
||
MP --> HC & BC & AC & OC & PC & IC & SC & TC
|
||
HC --> HS
|
||
BC --> BS
|
||
AC --> AS
|
||
OC --> OS
|
||
PC --> PS
|
||
IC --> IS
|
||
SC --> SS
|
||
TC --> TS
|
||
HS & BS & AS & OS & PS & IS & SS & TS --> DB
|
||
HS & BS & SS & TS --> Redis
|
||
```
|
||
|
||
### 项目结构
|
||
|
||
```
|
||
server/MiAssessment/src/MiAssessment.Api/
|
||
├── Controllers/
|
||
│ ├── HomeController.cs # 首页模块
|
||
│ ├── BusinessController.cs # 业务详情模块
|
||
│ ├── AssessmentController.cs # 测评模块
|
||
│ ├── OrderController.cs # 订单模块
|
||
│ ├── PlannerController.cs # 规划师模块
|
||
│ ├── InviteController.cs # 分销模块
|
||
│ ├── SystemController.cs # 系统模块
|
||
│ └── TeamController.cs # 团队模块
|
||
|
||
server/MiAssessment/src/MiAssessment.Core/
|
||
├── Interfaces/
|
||
│ ├── IHomeService.cs
|
||
│ ├── IBusinessService.cs
|
||
│ ├── IAssessmentService.cs
|
||
│ ├── IOrderService.cs
|
||
│ ├── IPlannerService.cs
|
||
│ ├── IInviteService.cs
|
||
│ ├── ISystemService.cs
|
||
│ └── ITeamService.cs
|
||
├── Services/
|
||
│ ├── HomeService.cs
|
||
│ ├── BusinessService.cs
|
||
│ ├── AssessmentService.cs
|
||
│ ├── OrderService.cs
|
||
│ ├── PlannerService.cs
|
||
│ ├── InviteService.cs
|
||
│ ├── SystemService.cs
|
||
│ └── TeamService.cs
|
||
|
||
server/MiAssessment/src/MiAssessment.Model/
|
||
├── Models/
|
||
│ ├── Home/
|
||
│ ├── Business/
|
||
│ ├── Assessment/
|
||
│ ├── Order/
|
||
│ ├── Planner/
|
||
│ ├── Invite/
|
||
│ ├── System/
|
||
│ └── Team/
|
||
├── Entities/
|
||
│ ├── Banner.cs
|
||
│ ├── Promotion.cs
|
||
│ ├── BusinessPage.cs
|
||
│ ├── Planner.cs
|
||
│ ├── PlannerBooking.cs
|
||
│ ├── InviteCode.cs
|
||
│ ├── Commission.cs
|
||
│ └── Withdrawal.cs
|
||
```
|
||
|
||
## Components and Interfaces
|
||
|
||
### 1. 首页模块 (HomeController / IHomeService)
|
||
|
||
#### 控制器接口
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api/home")]
|
||
public class HomeController : ControllerBase
|
||
{
|
||
/// <summary>
|
||
/// 获取Banner列表
|
||
/// GET /api/home/getBannerList
|
||
/// </summary>
|
||
[HttpGet("getBannerList")]
|
||
Task<ApiResponse<List<BannerDto>>> GetBannerList();
|
||
|
||
/// <summary>
|
||
/// 获取测评入口列表
|
||
/// GET /api/home/getAssessmentList
|
||
/// </summary>
|
||
[HttpGet("getAssessmentList")]
|
||
Task<ApiResponse<List<AssessmentTypeDto>>> GetAssessmentList();
|
||
|
||
/// <summary>
|
||
/// 获取宣传图列表
|
||
/// GET /api/home/getPromotionList
|
||
/// </summary>
|
||
[HttpGet("getPromotionList")]
|
||
Task<ApiResponse<List<PromotionDto>>> GetPromotionList();
|
||
}
|
||
```
|
||
|
||
#### 服务接口
|
||
|
||
```csharp
|
||
public interface IHomeService
|
||
{
|
||
Task<List<BannerDto>> GetBannerListAsync();
|
||
Task<List<AssessmentTypeDto>> GetAssessmentListAsync();
|
||
Task<List<PromotionDto>> GetPromotionListAsync();
|
||
}
|
||
```
|
||
|
||
### 2. 业务详情模块 (BusinessController / IBusinessService)
|
||
|
||
#### 控制器接口
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api/business")]
|
||
public class BusinessController : ControllerBase
|
||
{
|
||
/// <summary>
|
||
/// 获取业务详情
|
||
/// GET /api/business/getDetail?id=1
|
||
/// </summary>
|
||
[HttpGet("getDetail")]
|
||
Task<ApiResponse<BusinessDetailDto>> GetDetail([FromQuery] long id);
|
||
}
|
||
```
|
||
|
||
#### 服务接口
|
||
|
||
```csharp
|
||
public interface IBusinessService
|
||
{
|
||
Task<BusinessDetailDto?> GetDetailAsync(long id);
|
||
}
|
||
```
|
||
|
||
### 3. 测评模块 (AssessmentController / IAssessmentService)
|
||
|
||
#### 控制器接口
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api/assessment")]
|
||
public class AssessmentController : ControllerBase
|
||
{
|
||
/// <summary>
|
||
/// 获取测评介绍
|
||
/// GET /api/assessment/getIntro?typeId=1
|
||
/// </summary>
|
||
[HttpGet("getIntro")]
|
||
Task<ApiResponse<AssessmentIntroDto>> GetIntro([FromQuery] long typeId);
|
||
|
||
/// <summary>
|
||
/// 获取题目列表
|
||
/// GET /api/assessment/getQuestionList?typeId=1
|
||
/// </summary>
|
||
[HttpGet("getQuestionList")]
|
||
[Authorize]
|
||
Task<ApiResponse<List<QuestionDto>>> GetQuestionList([FromQuery] long typeId);
|
||
|
||
/// <summary>
|
||
/// 提交测评答案
|
||
/// POST /api/assessment/submitAnswers
|
||
/// </summary>
|
||
[HttpPost("submitAnswers")]
|
||
[Authorize]
|
||
Task<ApiResponse<SubmitAnswersResponse>> SubmitAnswers([FromBody] SubmitAnswersRequest request);
|
||
|
||
/// <summary>
|
||
/// 查询报告生成状态
|
||
/// GET /api/assessment/getResultStatus?recordId=1
|
||
/// </summary>
|
||
[HttpGet("getResultStatus")]
|
||
[Authorize]
|
||
Task<ApiResponse<ResultStatusDto>> GetResultStatus([FromQuery] long recordId);
|
||
|
||
/// <summary>
|
||
/// 获取测评结果
|
||
/// GET /api/assessment/getResult?recordId=1
|
||
/// </summary>
|
||
[HttpGet("getResult")]
|
||
[Authorize]
|
||
Task<ApiResponse<AssessmentResultDto>> GetResult([FromQuery] long recordId);
|
||
|
||
/// <summary>
|
||
/// 验证邀请码
|
||
/// POST /api/assessment/verifyInviteCode
|
||
/// </summary>
|
||
[HttpPost("verifyInviteCode")]
|
||
[Authorize]
|
||
Task<ApiResponse<VerifyInviteCodeResponse>> VerifyInviteCode([FromBody] VerifyInviteCodeRequest request);
|
||
|
||
/// <summary>
|
||
/// 获取往期测评列表
|
||
/// GET /api/assessment/getHistoryList?page=1&pageSize=20
|
||
/// </summary>
|
||
[HttpGet("getHistoryList")]
|
||
[Authorize]
|
||
Task<ApiResponse<PagedResult<AssessmentHistoryDto>>> GetHistoryList([FromQuery] int page = 1, [FromQuery] int pageSize = 20);
|
||
}
|
||
```
|
||
|
||
#### 服务接口
|
||
|
||
```csharp
|
||
public interface IAssessmentService
|
||
{
|
||
Task<AssessmentIntroDto?> GetIntroAsync(long typeId);
|
||
Task<List<QuestionDto>> GetQuestionListAsync(long typeId);
|
||
Task<SubmitAnswersResponse> SubmitAnswersAsync(long userId, SubmitAnswersRequest request);
|
||
Task<ResultStatusDto?> GetResultStatusAsync(long userId, long recordId);
|
||
Task<AssessmentResultDto?> GetResultAsync(long userId, long recordId);
|
||
Task<VerifyInviteCodeResponse> VerifyInviteCodeAsync(string code);
|
||
Task<PagedResult<AssessmentHistoryDto>> GetHistoryListAsync(long userId, int page, int pageSize);
|
||
}
|
||
```
|
||
|
||
### 4. 订单模块 (OrderController / IOrderService)
|
||
|
||
#### 控制器接口
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api/order")]
|
||
public class OrderController : ControllerBase
|
||
{
|
||
/// <summary>
|
||
/// 获取订单列表
|
||
/// GET /api/order/getList?page=1&pageSize=20&orderType=1
|
||
/// </summary>
|
||
[HttpGet("getList")]
|
||
[Authorize]
|
||
Task<ApiResponse<PagedResult<OrderItemDto>>> GetList([FromQuery] int page = 1, [FromQuery] int pageSize = 20, [FromQuery] int? orderType = null);
|
||
|
||
/// <summary>
|
||
/// 获取订单详情
|
||
/// GET /api/order/getDetail?orderId=1
|
||
/// </summary>
|
||
[HttpGet("getDetail")]
|
||
[Authorize]
|
||
Task<ApiResponse<OrderDetailDto>> GetDetail([FromQuery] long orderId);
|
||
|
||
/// <summary>
|
||
/// 创建订单
|
||
/// POST /api/order/create
|
||
/// </summary>
|
||
[HttpPost("create")]
|
||
[Authorize]
|
||
Task<ApiResponse<CreateOrderResponse>> Create([FromBody] CreateOrderRequest request);
|
||
|
||
/// <summary>
|
||
/// 发起支付
|
||
/// POST /api/order/pay
|
||
/// </summary>
|
||
[HttpPost("pay")]
|
||
[Authorize]
|
||
Task<ApiResponse<PayResponse>> Pay([FromBody] PayRequest request);
|
||
|
||
/// <summary>
|
||
/// 查询支付结果
|
||
/// GET /api/order/getPayResult?orderId=1
|
||
/// </summary>
|
||
[HttpGet("getPayResult")]
|
||
[Authorize]
|
||
Task<ApiResponse<PayResultDto>> GetPayResult([FromQuery] long orderId);
|
||
}
|
||
```
|
||
|
||
#### 服务接口
|
||
|
||
```csharp
|
||
public interface IOrderService
|
||
{
|
||
Task<PagedResult<OrderItemDto>> GetListAsync(long userId, int page, int pageSize, int? orderType);
|
||
Task<OrderDetailDto?> GetDetailAsync(long userId, long orderId);
|
||
Task<CreateOrderResponse> CreateAsync(long userId, CreateOrderRequest request);
|
||
Task<PayResponse> PayAsync(long userId, long orderId);
|
||
Task<PayResultDto?> GetPayResultAsync(long userId, long orderId);
|
||
}
|
||
```
|
||
|
||
### 5. 规划师模块 (PlannerController / IPlannerService)
|
||
|
||
#### 控制器接口
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api/planner")]
|
||
public class PlannerController : ControllerBase
|
||
{
|
||
/// <summary>
|
||
/// 获取规划师列表
|
||
/// GET /api/planner/getList
|
||
/// </summary>
|
||
[HttpGet("getList")]
|
||
Task<ApiResponse<List<PlannerDto>>> GetList();
|
||
}
|
||
```
|
||
|
||
#### 服务接口
|
||
|
||
```csharp
|
||
public interface IPlannerService
|
||
{
|
||
Task<List<PlannerDto>> GetListAsync();
|
||
}
|
||
```
|
||
|
||
### 6. 分销模块 (InviteController / IInviteService)
|
||
|
||
#### 控制器接口
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api/invite")]
|
||
public class InviteController : ControllerBase
|
||
{
|
||
/// <summary>
|
||
/// 获取邀请信息
|
||
/// GET /api/invite/getInfo
|
||
/// </summary>
|
||
[HttpGet("getInfo")]
|
||
[Authorize]
|
||
Task<ApiResponse<InviteInfoDto>> GetInfo();
|
||
|
||
/// <summary>
|
||
/// 生成邀请二维码
|
||
/// GET /api/invite/getQrcode
|
||
/// </summary>
|
||
[HttpGet("getQrcode")]
|
||
[Authorize]
|
||
Task<ApiResponse<QrcodeDto>> GetQrcode();
|
||
|
||
/// <summary>
|
||
/// 获取邀请记录
|
||
/// GET /api/invite/getRecordList?page=1&pageSize=20
|
||
/// </summary>
|
||
[HttpGet("getRecordList")]
|
||
[Authorize]
|
||
Task<ApiResponse<PagedResult<InviteRecordDto>>> GetRecordList([FromQuery] int page = 1, [FromQuery] int pageSize = 20);
|
||
|
||
/// <summary>
|
||
/// 获取佣金信息
|
||
/// GET /api/invite/getCommission
|
||
/// </summary>
|
||
[HttpGet("getCommission")]
|
||
[Authorize]
|
||
Task<ApiResponse<CommissionInfoDto>> GetCommission();
|
||
|
||
/// <summary>
|
||
/// 申请提现
|
||
/// POST /api/invite/applyWithdraw
|
||
/// </summary>
|
||
[HttpPost("applyWithdraw")]
|
||
[Authorize]
|
||
Task<ApiResponse<ApplyWithdrawResponse>> ApplyWithdraw([FromBody] ApplyWithdrawRequest request);
|
||
|
||
/// <summary>
|
||
/// 获取提现记录
|
||
/// GET /api/invite/getWithdrawList?page=1&pageSize=20
|
||
/// </summary>
|
||
[HttpGet("getWithdrawList")]
|
||
[Authorize]
|
||
Task<ApiResponse<PagedResult<WithdrawRecordDto>>> GetWithdrawList([FromQuery] int page = 1, [FromQuery] int pageSize = 20);
|
||
}
|
||
```
|
||
|
||
#### 服务接口
|
||
|
||
```csharp
|
||
public interface IInviteService
|
||
{
|
||
Task<InviteInfoDto> GetInfoAsync(long userId);
|
||
Task<QrcodeDto> GetQrcodeAsync(long userId);
|
||
Task<PagedResult<InviteRecordDto>> GetRecordListAsync(long userId, int page, int pageSize);
|
||
Task<CommissionInfoDto> GetCommissionAsync(long userId);
|
||
Task<ApplyWithdrawResponse> ApplyWithdrawAsync(long userId, decimal amount);
|
||
Task<PagedResult<WithdrawRecordDto>> GetWithdrawListAsync(long userId, int page, int pageSize);
|
||
}
|
||
```
|
||
|
||
### 7. 系统模块 (SystemController / ISystemService)
|
||
|
||
#### 控制器接口
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api/system")]
|
||
public class SystemController : ControllerBase
|
||
{
|
||
/// <summary>
|
||
/// 获取用户协议
|
||
/// GET /api/system/getAgreement
|
||
/// </summary>
|
||
[HttpGet("getAgreement")]
|
||
Task<ApiResponse<AgreementDto>> GetAgreement();
|
||
|
||
/// <summary>
|
||
/// 获取隐私政策
|
||
/// GET /api/system/getPrivacy
|
||
/// </summary>
|
||
[HttpGet("getPrivacy")]
|
||
Task<ApiResponse<PrivacyDto>> GetPrivacy();
|
||
|
||
/// <summary>
|
||
/// 获取关于我们
|
||
/// GET /api/system/getAbout
|
||
/// </summary>
|
||
[HttpGet("getAbout")]
|
||
Task<ApiResponse<AboutDto>> GetAbout();
|
||
}
|
||
```
|
||
|
||
#### 服务接口
|
||
|
||
```csharp
|
||
public interface ISystemService
|
||
{
|
||
Task<AgreementDto> GetAgreementAsync();
|
||
Task<PrivacyDto> GetPrivacyAsync();
|
||
Task<AboutDto> GetAboutAsync();
|
||
}
|
||
```
|
||
|
||
### 8. 团队模块 (TeamController / ITeamService)
|
||
|
||
#### 控制器接口
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api/team")]
|
||
public class TeamController : ControllerBase
|
||
{
|
||
/// <summary>
|
||
/// 获取团队介绍
|
||
/// GET /api/team/getInfo
|
||
/// </summary>
|
||
[HttpGet("getInfo")]
|
||
Task<ApiResponse<TeamInfoDto>> GetInfo();
|
||
}
|
||
```
|
||
|
||
#### 服务接口
|
||
|
||
```csharp
|
||
public interface ITeamService
|
||
{
|
||
Task<TeamInfoDto> GetInfoAsync();
|
||
}
|
||
```
|
||
|
||
## Data Models
|
||
|
||
### 实体类 (Entities)
|
||
|
||
#### Banner.cs
|
||
```csharp
|
||
[Table("banners")]
|
||
public class Banner
|
||
{
|
||
[Key]
|
||
public long Id { get; set; }
|
||
public string? Title { get; set; }
|
||
[Required]
|
||
public string ImageUrl { get; set; } = null!;
|
||
public int LinkType { get; set; } // 0无 1内部页面 2外部链接 3小程序
|
||
public string? LinkUrl { get; set; }
|
||
public string? AppId { get; set; }
|
||
public int Sort { get; set; }
|
||
public int Status { get; set; } // 0禁用 1启用
|
||
public DateTime CreateTime { get; set; }
|
||
public DateTime UpdateTime { get; set; }
|
||
public bool IsDeleted { get; set; }
|
||
}
|
||
```
|
||
|
||
#### Promotion.cs
|
||
```csharp
|
||
[Table("promotions")]
|
||
public class Promotion
|
||
{
|
||
[Key]
|
||
public long Id { get; set; }
|
||
public string? Title { get; set; }
|
||
[Required]
|
||
public string ImageUrl { get; set; } = null!;
|
||
public int Position { get; set; } // 1首页底部 2团队页
|
||
public int Sort { get; set; }
|
||
public int Status { get; set; }
|
||
public DateTime CreateTime { get; set; }
|
||
public DateTime UpdateTime { get; set; }
|
||
public bool IsDeleted { get; set; }
|
||
}
|
||
```
|
||
|
||
#### BusinessPage.cs
|
||
```csharp
|
||
[Table("business_pages")]
|
||
public class BusinessPage
|
||
{
|
||
[Key]
|
||
public long Id { get; set; }
|
||
public string? Title { get; set; }
|
||
[Required]
|
||
public string ImageUrl { get; set; } = null!;
|
||
public bool ShowButton { get; set; }
|
||
public string? ButtonText { get; set; }
|
||
public string? ButtonLink { get; set; }
|
||
public int Sort { get; set; }
|
||
public int Status { get; set; }
|
||
public DateTime CreateTime { get; set; }
|
||
public DateTime UpdateTime { get; set; }
|
||
public bool IsDeleted { get; set; }
|
||
}
|
||
```
|
||
|
||
#### Planner.cs
|
||
```csharp
|
||
[Table("planners")]
|
||
public class Planner
|
||
{
|
||
[Key]
|
||
public long Id { get; set; }
|
||
[Required]
|
||
public string Name { get; set; } = null!;
|
||
[Required]
|
||
public string Avatar { get; set; } = null!;
|
||
public string? Introduction { get; set; }
|
||
public decimal Price { get; set; }
|
||
public int Sort { get; set; }
|
||
public int Status { get; set; }
|
||
public DateTime CreateTime { get; set; }
|
||
public DateTime UpdateTime { get; set; }
|
||
public bool IsDeleted { get; set; }
|
||
}
|
||
```
|
||
|
||
#### InviteCode.cs
|
||
```csharp
|
||
[Table("invite_codes")]
|
||
public class InviteCode
|
||
{
|
||
[Key]
|
||
public long Id { get; set; }
|
||
[Required]
|
||
public string Code { get; set; } = null!;
|
||
public string? BatchNo { get; set; }
|
||
public long? AssignUserId { get; set; }
|
||
public DateTime? AssignTime { get; set; }
|
||
public long? UseUserId { get; set; }
|
||
public long? UseOrderId { get; set; }
|
||
public DateTime? UseTime { get; set; }
|
||
public int Status { get; set; } // 1未分配 2已分配 3已使用
|
||
public DateTime CreateTime { get; set; }
|
||
public DateTime UpdateTime { get; set; }
|
||
public bool IsDeleted { get; set; }
|
||
}
|
||
```
|
||
|
||
#### Commission.cs
|
||
```csharp
|
||
[Table("commissions")]
|
||
public class Commission
|
||
{
|
||
[Key]
|
||
public long Id { get; set; }
|
||
public long UserId { get; set; }
|
||
public long FromUserId { get; set; }
|
||
public long OrderId { get; set; }
|
||
public decimal OrderAmount { get; set; }
|
||
public decimal CommissionRate { get; set; }
|
||
public decimal CommissionAmount { get; set; }
|
||
public int Level { get; set; } // 1直接下级 2间接下级
|
||
public int Status { get; set; } // 1待结算 2已结算
|
||
public DateTime? SettleTime { get; set; }
|
||
public DateTime CreateTime { get; set; }
|
||
public DateTime UpdateTime { get; set; }
|
||
public bool IsDeleted { get; set; }
|
||
}
|
||
```
|
||
|
||
#### Withdrawal.cs
|
||
```csharp
|
||
[Table("withdrawals")]
|
||
public class Withdrawal
|
||
{
|
||
[Key]
|
||
public long Id { get; set; }
|
||
[Required]
|
||
public string WithdrawalNo { get; set; } = null!;
|
||
public long UserId { get; set; }
|
||
public decimal Amount { get; set; }
|
||
public decimal BeforeBalance { get; set; }
|
||
public decimal AfterBalance { get; set; }
|
||
public int Status { get; set; } // 1申请中 2提现中 3已提现 4已取消
|
||
public long? AuditUserId { 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; }
|
||
public DateTime UpdateTime { get; set; }
|
||
public bool IsDeleted { get; set; }
|
||
}
|
||
```
|
||
|
||
### DTO模型
|
||
|
||
#### Home模块
|
||
```csharp
|
||
public class BannerDto
|
||
{
|
||
public long Id { get; set; }
|
||
public string? Title { get; set; }
|
||
public string ImageUrl { get; set; } = null!;
|
||
public int LinkType { get; set; }
|
||
public string? LinkUrl { get; set; }
|
||
public string? AppId { get; set; }
|
||
}
|
||
|
||
public class AssessmentTypeDto
|
||
{
|
||
public long Id { get; set; }
|
||
public string Name { get; set; } = null!;
|
||
public string Code { get; set; } = null!;
|
||
public string ImageUrl { get; set; } = null!;
|
||
public decimal Price { get; set; }
|
||
public int Status { get; set; }
|
||
}
|
||
|
||
public class PromotionDto
|
||
{
|
||
public long Id { get; set; }
|
||
public string? Title { get; set; }
|
||
public string ImageUrl { get; set; } = null!;
|
||
}
|
||
```
|
||
|
||
#### Business模块
|
||
```csharp
|
||
public class BusinessDetailDto
|
||
{
|
||
public long Id { get; set; }
|
||
public string? Title { get; set; }
|
||
public string ImageUrl { get; set; } = null!;
|
||
public bool ShowButton { get; set; }
|
||
public string? ButtonText { get; set; }
|
||
public string? ButtonLink { get; set; }
|
||
}
|
||
```
|
||
|
||
#### Assessment模块
|
||
```csharp
|
||
public class AssessmentIntroDto
|
||
{
|
||
public string Content { get; set; } = null!;
|
||
public string ContentType { get; set; } = null!;
|
||
public decimal Price { get; set; }
|
||
}
|
||
|
||
public class QuestionDto
|
||
{
|
||
public long Id { get; set; }
|
||
public int QuestionNo { get; set; }
|
||
public string Content { get; set; } = null!;
|
||
}
|
||
|
||
public class SubmitAnswersRequest
|
||
{
|
||
public long RecordId { get; set; }
|
||
public List<AnswerItem> Answers { get; set; } = new();
|
||
}
|
||
|
||
public class AnswerItem
|
||
{
|
||
public long QuestionId { get; set; }
|
||
public int AnswerValue { get; set; }
|
||
}
|
||
|
||
public class SubmitAnswersResponse
|
||
{
|
||
public bool Success { get; set; }
|
||
public int EstimatedTime { get; set; }
|
||
}
|
||
|
||
public class ResultStatusDto
|
||
{
|
||
public int Status { get; set; }
|
||
public bool IsCompleted { get; set; }
|
||
}
|
||
|
||
public class VerifyInviteCodeRequest
|
||
{
|
||
public string Code { get; set; } = null!;
|
||
}
|
||
|
||
public class VerifyInviteCodeResponse
|
||
{
|
||
public bool IsValid { get; set; }
|
||
public string? ErrorMessage { get; set; }
|
||
public long? InviteCodeId { get; set; }
|
||
}
|
||
|
||
public class AssessmentHistoryDto
|
||
{
|
||
public long Id { get; set; }
|
||
public string OrderNo { get; set; } = null!;
|
||
public string AssessmentName { get; set; } = null!;
|
||
public int Status { get; set; }
|
||
public string StatusText { get; set; } = null!;
|
||
public string TestDate { get; set; } = null!;
|
||
}
|
||
```
|
||
|
||
#### Order模块
|
||
```csharp
|
||
public class OrderItemDto
|
||
{
|
||
public long Id { get; set; }
|
||
public string OrderNo { get; set; } = null!;
|
||
public int OrderType { get; set; }
|
||
public string ProductName { get; set; } = null!;
|
||
public decimal Amount { get; set; }
|
||
public int Status { get; set; }
|
||
public string StatusText { get; set; } = null!;
|
||
public string CreateTime { get; set; } = null!;
|
||
public long? AssessmentRecordId { get; set; }
|
||
}
|
||
|
||
public class CreateOrderRequest
|
||
{
|
||
public int OrderType { get; set; }
|
||
public long ProductId { get; set; }
|
||
public AssessmentInfoDto? AssessmentInfo { get; set; }
|
||
public PlannerInfoDto? PlannerInfo { get; set; }
|
||
public long? InviteCodeId { get; set; }
|
||
}
|
||
|
||
public class AssessmentInfoDto
|
||
{
|
||
public string Name { get; set; } = null!;
|
||
public string Phone { get; set; } = null!;
|
||
public int Gender { get; set; }
|
||
public int Age { get; set; }
|
||
public int EducationStage { get; set; }
|
||
public string Province { get; set; } = null!;
|
||
public string City { get; set; } = null!;
|
||
public string District { get; set; } = null!;
|
||
}
|
||
|
||
public class CreateOrderResponse
|
||
{
|
||
public long OrderId { get; set; }
|
||
public string OrderNo { get; set; } = null!;
|
||
public decimal PayAmount { get; set; }
|
||
public bool NeedPay { get; set; }
|
||
public long? AssessmentRecordId { get; set; }
|
||
}
|
||
|
||
public class PayRequest
|
||
{
|
||
public long OrderId { get; set; }
|
||
}
|
||
|
||
public class PayResponse
|
||
{
|
||
public string TimeStamp { get; set; } = null!;
|
||
public string NonceStr { get; set; } = null!;
|
||
public string Package { get; set; } = null!;
|
||
public string SignType { get; set; } = null!;
|
||
public string PaySign { get; set; } = null!;
|
||
}
|
||
|
||
public class PayResultDto
|
||
{
|
||
public bool IsPaid { get; set; }
|
||
public int Status { get; set; }
|
||
public long? AssessmentRecordId { get; set; }
|
||
}
|
||
```
|
||
|
||
#### Invite模块
|
||
```csharp
|
||
public class InviteInfoDto
|
||
{
|
||
public string InviteUrl { get; set; } = null!;
|
||
public string InviteCode { get; set; } = null!;
|
||
public decimal WithdrawnAmount { get; set; }
|
||
public decimal PendingAmount { get; set; }
|
||
public int InviteCount { get; set; }
|
||
}
|
||
|
||
public class QrcodeDto
|
||
{
|
||
public string QrcodeUrl { get; set; } = null!;
|
||
}
|
||
|
||
public class InviteRecordDto
|
||
{
|
||
public long UserId { get; set; }
|
||
public string Nickname { get; set; } = null!;
|
||
public string Avatar { get; set; } = null!;
|
||
public string Uid { get; set; } = null!;
|
||
public string RegisterDate { get; set; } = null!;
|
||
public decimal Commission { get; set; }
|
||
}
|
||
|
||
public class CommissionInfoDto
|
||
{
|
||
public decimal TotalIncome { get; set; }
|
||
public decimal WithdrawnAmount { get; set; }
|
||
public decimal PendingAmount { get; set; }
|
||
}
|
||
|
||
public class ApplyWithdrawRequest
|
||
{
|
||
public decimal Amount { get; set; }
|
||
}
|
||
|
||
public class ApplyWithdrawResponse
|
||
{
|
||
public bool Success { get; set; }
|
||
public string? ErrorMessage { get; set; }
|
||
public string? WithdrawalNo { get; set; }
|
||
}
|
||
|
||
public class WithdrawRecordDto
|
||
{
|
||
public long Id { get; set; }
|
||
public string WithdrawalNo { get; set; } = null!;
|
||
public decimal Amount { get; set; }
|
||
public int Status { get; set; }
|
||
public string StatusText { get; set; } = null!;
|
||
public string CreateTime { get; set; } = null!;
|
||
public string? PayTime { get; set; }
|
||
}
|
||
```
|
||
|
||
#### System模块
|
||
```csharp
|
||
public class AgreementDto
|
||
{
|
||
public string Content { get; set; } = null!;
|
||
}
|
||
|
||
public class PrivacyDto
|
||
{
|
||
public string Content { get; set; } = null!;
|
||
}
|
||
|
||
public class AboutDto
|
||
{
|
||
public string Content { get; set; } = null!;
|
||
public string Version { get; set; } = null!;
|
||
}
|
||
```
|
||
|
||
#### Team模块
|
||
```csharp
|
||
public class TeamInfoDto
|
||
{
|
||
public List<string> Images { get; set; } = new();
|
||
}
|
||
```
|
||
|
||
|
||
|
||
## 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.*
|
||
|
||
### Property 1: 列表查询过滤正确性
|
||
|
||
*For any* 列表查询接口(Banner、测评类型、宣传图、题目、规划师),返回的所有记录都应满足指定的过滤条件(Status、Position、IsDeleted等),且不包含任何不满足条件的记录。
|
||
|
||
**Validates: Requirements 1.1, 1.2, 1.3, 3.2, 10.1, 15.1**
|
||
|
||
### Property 2: 列表查询排序正确性
|
||
|
||
*For any* 需要排序的列表查询接口,返回的记录应按指定字段(Sort、QuestionNo等)正确排序,相邻记录的排序字段值应满足排序规则。
|
||
|
||
**Validates: Requirements 1.1, 3.2, 10.1**
|
||
|
||
### Property 3: 分页查询一致性
|
||
|
||
*For any* 分页查询接口,返回的记录数不应超过pageSize,total应等于满足条件的总记录数,且遍历所有页面应能获取所有满足条件的记录。
|
||
|
||
**Validates: Requirements 6.1, 7.1, 12.1, 13.5, 16.4**
|
||
|
||
### Property 4: 用户数据隔离
|
||
|
||
*For any* 需要用户认证的查询接口(订单、测评记录、邀请记录等),返回的数据应只属于当前登录用户,不应包含其他用户的数据。
|
||
|
||
**Validates: Requirements 4.3, 4.5, 6.1, 7.1, 7.2, 7.3**
|
||
|
||
### Property 5: 订单创建完整性
|
||
|
||
*For any* 订单创建请求,成功创建后应同时存在订单记录和关联记录(测评记录或规划预约),且关联记录的OrderId应指向新创建的订单。
|
||
|
||
**Validates: Requirements 8.1, 8.2**
|
||
|
||
### Property 6: 邀请码使用一次性
|
||
|
||
*For any* 有效的邀请码,使用后其状态应变为"已使用",且不能被再次使用;对于已使用的邀请码,验证接口应返回"已被使用"错误。
|
||
|
||
**Validates: Requirements 5.1, 5.3, 5.4, 8.3**
|
||
|
||
### Property 7: 提现余额一致性
|
||
|
||
*For any* 提现申请,提现后用户余额应等于提现前余额减去提现金额,且提现记录中的BeforeBalance和AfterBalance应正确记录变化。
|
||
|
||
**Validates: Requirements 13.1**
|
||
|
||
### Property 8: 响应格式一致性
|
||
|
||
*For any* API接口调用,响应应包含code、message、data三个字段,成功时code=0,失败时code!=0且message包含错误信息。
|
||
|
||
**Validates: Requirements 16.1, 16.2, 16.3**
|
||
|
||
### Property 9: 测评答案提交状态变更
|
||
|
||
*For any* 有效的测评答案提交,提交后测评记录的状态应从"待测评"或"测评中"变为"生成中",且答案数量应等于题目数量。
|
||
|
||
**Validates: Requirements 4.1, 4.2**
|
||
|
||
### Property 10: 订单事务回滚
|
||
|
||
*For any* 订单创建过程中发生的错误,数据库中不应存在部分创建的订单或关联记录,所有操作应完全回滚。
|
||
|
||
**Validates: Requirements 8.4**
|
||
|
||
## Error Handling
|
||
|
||
### 错误码定义
|
||
|
||
| 错误码 | 说明 | 使用场景 |
|
||
|--------|------|----------|
|
||
| 0 | 成功 | 所有成功响应 |
|
||
| -1 | 未登录 | Token无效或未提供 |
|
||
| 1001 | 参数错误 | 请求参数验证失败 |
|
||
| 1002 | 未登录 | 需要认证但未提供Token |
|
||
| 1003 | 登录已过期 | Token已过期 |
|
||
| 1004 | 无权限 | 访问非本人数据 |
|
||
| 2001 | 业务错误 | 通用业务逻辑错误 |
|
||
| 2002 | 数据不存在 | 查询的记录不存在 |
|
||
| 2003 | 状态错误 | 操作的记录状态不允许 |
|
||
| 2004 | 邀请码无效 | 邀请码不存在或已使用 |
|
||
| 2005 | 余额不足 | 提现金额超过可用余额 |
|
||
| 5000 | 系统错误 | 未预期的系统异常 |
|
||
|
||
### 错误处理策略
|
||
|
||
#### 参数验证错误
|
||
```csharp
|
||
// 在控制器层进行参数验证
|
||
if (string.IsNullOrWhiteSpace(request.Code))
|
||
{
|
||
return ApiResponse<T>.Fail("邀请码不能为空", 1001);
|
||
}
|
||
```
|
||
|
||
#### 业务逻辑错误
|
||
```csharp
|
||
// 在服务层抛出业务异常
|
||
if (inviteCode == null)
|
||
{
|
||
throw new BusinessException("邀请码有误,请重新输入", 2004);
|
||
}
|
||
|
||
if (inviteCode.Status == 3)
|
||
{
|
||
throw new BusinessException("邀请码已被使用", 2004);
|
||
}
|
||
```
|
||
|
||
#### 权限验证错误
|
||
```csharp
|
||
// 验证数据归属
|
||
if (record.UserId != userId)
|
||
{
|
||
return ApiResponse<T>.Fail("无权限访问", 1004);
|
||
}
|
||
```
|
||
|
||
#### 事务回滚
|
||
```csharp
|
||
using var transaction = await _dbContext.Database.BeginTransactionAsync();
|
||
try
|
||
{
|
||
// 多表操作
|
||
await _dbContext.SaveChangesAsync();
|
||
await transaction.CommitAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await transaction.RollbackAsync();
|
||
_logger.LogError(ex, "创建订单失败");
|
||
throw;
|
||
}
|
||
```
|
||
|
||
### 全局异常处理
|
||
|
||
```csharp
|
||
public class GlobalExceptionMiddleware
|
||
{
|
||
public async Task InvokeAsync(HttpContext context)
|
||
{
|
||
try
|
||
{
|
||
await _next(context);
|
||
}
|
||
catch (BusinessException ex)
|
||
{
|
||
await HandleBusinessExceptionAsync(context, ex);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "系统异常");
|
||
await HandleSystemExceptionAsync(context, ex);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Testing Strategy
|
||
|
||
### 测试框架
|
||
|
||
- 单元测试:xUnit + Moq
|
||
- 属性测试:FsCheck
|
||
- 集成测试:WebApplicationFactory
|
||
|
||
### 单元测试
|
||
|
||
单元测试用于验证具体的业务逻辑和边界条件:
|
||
|
||
1. **服务层测试**:验证业务逻辑正确性
|
||
2. **边界条件测试**:验证错误处理逻辑
|
||
3. **数据验证测试**:验证参数验证逻辑
|
||
|
||
### 属性测试
|
||
|
||
属性测试用于验证通用属性在各种输入下都成立:
|
||
|
||
```csharp
|
||
/// <summary>
|
||
/// Property 1: 列表查询过滤正确性
|
||
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
|
||
/// **Validates: Requirements 1.1, 1.2, 1.3, 3.2, 10.1, 15.1**
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property BannerListOnlyReturnsEnabledRecords()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.From<List<Banner>>(),
|
||
banners =>
|
||
{
|
||
// Arrange: 设置测试数据
|
||
// Act: 调用服务方法
|
||
// Assert: 验证返回结果只包含Status=1的记录
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 3: 分页查询一致性
|
||
/// **Feature: miniapp-api, Property 3: 分页查询一致性**
|
||
/// **Validates: Requirements 6.1, 7.1, 12.1, 13.5, 16.4**
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property PaginationReturnsCorrectCount()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.From<int>().Filter(x => x > 0 && x <= 100),
|
||
Arb.From<int>().Filter(x => x > 0 && x <= 50),
|
||
(page, pageSize) =>
|
||
{
|
||
// 验证分页返回的记录数不超过pageSize
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 7: 提现余额一致性
|
||
/// **Feature: miniapp-api, Property 7: 提现余额一致性**
|
||
/// **Validates: Requirements 13.1**
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property WithdrawDeductsCorrectAmount()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.From<decimal>().Filter(x => x >= 1 && x <= 10000),
|
||
Arb.From<decimal>().Filter(x => x >= 1 && x <= 10000),
|
||
(balance, withdrawAmount) =>
|
||
{
|
||
// 验证提现后余额 = 提现前余额 - 提现金额
|
||
});
|
||
}
|
||
```
|
||
|
||
### 集成测试
|
||
|
||
集成测试用于验证API端到端的正确性:
|
||
|
||
```csharp
|
||
public class HomeControllerTests : IClassFixture<WebApplicationFactory<Program>>
|
||
{
|
||
[Fact]
|
||
public async Task GetBannerList_ReturnsOnlyEnabledBanners()
|
||
{
|
||
// Arrange
|
||
var client = _factory.CreateClient();
|
||
|
||
// Act
|
||
var response = await client.GetAsync("/api/home/getBannerList");
|
||
|
||
// Assert
|
||
response.EnsureSuccessStatusCode();
|
||
var result = await response.Content.ReadFromJsonAsync<ApiResponse<List<BannerDto>>>();
|
||
Assert.Equal(0, result.Code);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 测试覆盖要求
|
||
|
||
| 测试类型 | 覆盖范围 | 最低要求 |
|
||
|----------|----------|----------|
|
||
| 单元测试 | 服务层方法 | 80% |
|
||
| 属性测试 | 核心业务属性 | 10个属性 |
|
||
| 集成测试 | API接口 | 所有28个接口 |
|
||
|
||
### 关键测试场景
|
||
|
||
| 场景 | 测试点 |
|
||
|------|--------|
|
||
| 订单创建 | 测评订单、规划订单、使用邀请码、事务回滚 |
|
||
| 提交答案 | 完整答案、部分答案、重复提交、权限验证 |
|
||
| 申请提现 | 余额充足、余额不足、最低金额、非整数金额 |
|
||
| 分页查询 | 首页、末页、空数据、超出范围 |
|
||
| 认证授权 | 无Token、过期Token、无效Token |
|