mi-assessment/.kiro/specs/miniapp-api/design.md
zpc af4b68c2dd docs: 添加小程序API开发规划文档
- 创建 miniapp-api spec (requirements, design, tasks)
- 添加 API开发任务清单,包含页面-API对照验证
- 规划28个待开发接口,按P0/P1/P2优先级排列
2026-02-09 08:35:43 +08:00

32 KiB
Raw Blame History

Design Document: 小程序API开发

Overview

本设计文档描述学业邑规划MiAssessment微信小程序后端API的技术实现方案。系统采用分层架构遵循.NET Web API最佳实践实现28个API接口覆盖8个业务模块。

技术栈

  • 后端框架:.NET 10 Web API (C#)
  • 数据库SQL Server 2022
  • ORMEntity 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 分页查询接口返回的记录数不应超过pageSizetotal应等于满足条件的总记录数且遍历所有页面应能获取所有满足条件的记录。

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

单元测试

单元测试用于验证具体的业务逻辑和边界条件:

  1. 服务层测试:验证业务逻辑正确性
  2. 边界条件测试:验证错误处理逻辑
  3. 数据验证测试:验证参数验证逻辑

属性测试

属性测试用于验证通用属性在各种输入下都成立:

/// <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