- 创建 miniapp-api spec (requirements, design, tasks) - 添加 API开发任务清单,包含页面-API对照验证 - 规划28个待开发接口,按P0/P1/P2优先级排列
32 KiB
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
系统架构图
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)
控制器接口
[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();
}
服务接口
public interface IHomeService
{
Task<List<BannerDto>> GetBannerListAsync();
Task<List<AssessmentTypeDto>> GetAssessmentListAsync();
Task<List<PromotionDto>> GetPromotionListAsync();
}
2. 业务详情模块 (BusinessController / IBusinessService)
控制器接口
[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);
}
服务接口
public interface IBusinessService
{
Task<BusinessDetailDto?> GetDetailAsync(long id);
}
3. 测评模块 (AssessmentController / IAssessmentService)
控制器接口
[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);
}
服务接口
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)
控制器接口
[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);
}
服务接口
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)
控制器接口
[ApiController]
[Route("api/planner")]
public class PlannerController : ControllerBase
{
/// <summary>
/// 获取规划师列表
/// GET /api/planner/getList
/// </summary>
[HttpGet("getList")]
Task<ApiResponse<List<PlannerDto>>> GetList();
}
服务接口
public interface IPlannerService
{
Task<List<PlannerDto>> GetListAsync();
}
6. 分销模块 (InviteController / IInviteService)
控制器接口
[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);
}
服务接口
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)
控制器接口
[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();
}
服务接口
public interface ISystemService
{
Task<AgreementDto> GetAgreementAsync();
Task<PrivacyDto> GetPrivacyAsync();
Task<AboutDto> GetAboutAsync();
}
8. 团队模块 (TeamController / ITeamService)
控制器接口
[ApiController]
[Route("api/team")]
public class TeamController : ControllerBase
{
/// <summary>
/// 获取团队介绍
/// GET /api/team/getInfo
/// </summary>
[HttpGet("getInfo")]
Task<ApiResponse<TeamInfoDto>> GetInfo();
}
服务接口
public interface ITeamService
{
Task<TeamInfoDto> GetInfoAsync();
}
Data Models
实体类 (Entities)
Banner.cs
[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
[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
[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
[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
[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
[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
[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模块
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模块
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模块
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模块
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模块
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模块
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模块
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 | 系统错误 | 未预期的系统异常 |
错误处理策略
参数验证错误
// 在控制器层进行参数验证
if (string.IsNullOrWhiteSpace(request.Code))
{
return ApiResponse<T>.Fail("邀请码不能为空", 1001);
}
业务逻辑错误
// 在服务层抛出业务异常
if (inviteCode == null)
{
throw new BusinessException("邀请码有误,请重新输入", 2004);
}
if (inviteCode.Status == 3)
{
throw new BusinessException("邀请码已被使用", 2004);
}
权限验证错误
// 验证数据归属
if (record.UserId != userId)
{
return ApiResponse<T>.Fail("无权限访问", 1004);
}
事务回滚
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
// 多表操作
await _dbContext.SaveChangesAsync();
await transaction.CommitAsync();
}
catch (Exception ex)
{
await transaction.RollbackAsync();
_logger.LogError(ex, "创建订单失败");
throw;
}
全局异常处理
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
单元测试
单元测试用于验证具体的业务逻辑和边界条件:
- 服务层测试:验证业务逻辑正确性
- 边界条件测试:验证错误处理逻辑
- 数据验证测试:验证参数验证逻辑
属性测试
属性测试用于验证通用属性在各种输入下都成立:
/// <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端到端的正确性:
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 |