commit fb4f15ab33f8f3ade4c765e5d928ee39a80e21f5 Author: MSuMshk <2039814060@qq.com> Date: Mon Feb 9 20:00:18 2026 +0800 feat: 完成租户个人中心 API 首版并同步契约文档 diff --git a/TakeoutSaaS.TenantApi b/TakeoutSaaS.TenantApi new file mode 160000 index 0000000..f61554f --- /dev/null +++ b/TakeoutSaaS.TenantApi @@ -0,0 +1 @@ +Subproject commit f61554fc08cd0a340412475878f5706203798aa6 diff --git a/personal-center-prd.md b/personal-center-prd.md new file mode 100644 index 0000000..6bad43d --- /dev/null +++ b/personal-center-prd.md @@ -0,0 +1,138 @@ +# TenantUI 个人中心(第一版)需求草案 + +> 更新时间:2026-02-09 +> 结论来源:当前 Swagger + 数据库表结构与抽样数据(不改代码) + +## 1. 背景与目标 + +当前租户端右上角已新增“个人中心”入口,但页面还是占位。 +目标是先定义一版可落地的“个人中心可查询内容”,明确: + +1. 哪些内容数据库已有且有数据。 +2. 哪些内容前端可直接复用现有接口。 +3. 哪些内容需要后端补接口。 + +## 2. 当前接口现状(Swagger) + +已按规范拉取最新 Swagger:`https://api-tenant-dev.laosankeji.com/swagger/v1/swagger.json`。 + +当前仅 7 个路径: + +- `/api/tenant/v1/auth/login` +- `/api/tenant/v1/auth/menu` +- `/api/tenant/v1/auth/permissions` +- `/api/tenant/v1/auth/profile` +- `/api/tenant/v1/auth/refresh` +- `/api/tenant/v1/Health` +- `/api/tenant/v1/merchant/info` + +结论:个人中心能直接用的接口主要是 `/auth/profile`、`/auth/permissions`、`/merchant/info`。 + +## 3. 数据源盘点(按库) + +### 3.1 账号与权限(takeout_identity) + +- `identity_users`:账号、昵称、头像、手机号、邮箱、最后登录、失败次数、锁定时间、是否强制改密、状态、门户。 +- `user_roles` + `roles`:用户角色关系、角色名称与角色编码。 +- `permissions` + `role_permissions`:权限项和角色权限关系。 + +### 3.2 商户与租户(takeout_app) + +- `merchants`:品牌名、联系方式、经营模式、状态、是否冻结、加入/审核时间等。 +- `merchant_documents`:资质类型、状态、证号、有效期。 +- `tenant_subscriptions` + `tenant_packages`:套餐名、生效/到期、续费、计费日期。 +- `tenant_quota_usages`:额度上限与已用量。 +- `tenant_billing_statements` + `tenant_payments`:账单与支付记录。 +- `tenants`:租户基础信息(名称、联系人、状态、有效期等)。 + +### 3.3 日志与记录(takeout_logs) + +- `operation_logs`:操作类型、操作对象、操作者、成功与否、时间。 +- `merchant_audit_logs`:商户审核动作历史。 +- `merchant_change_logs`:商户字段变更历史。 +- `tenant_audit_logs`:租户审核动作历史。 + +## 4. 个人中心可查询内容建议(优先级) + +## P0(先做,首版上线) + +1. **我的账号** + - 账号、昵称、头像、手机号、邮箱、注册时间。 + - 数据源:`identity_users`。 +2. **账号安全** + - 最近登录时间、登录失败次数、锁定状态、是否强制改密。 + - 数据源:`identity_users`。 +3. **我的角色与权限概览** + - 角色名称/编码、权限数量。 + - 数据源:`user_roles`、`roles`、`role_permissions`。 +4. **我的归属信息** + - 租户名称、商户名称、商户状态、套餐名称、到期时间。 + - 数据源:`identity_users` + `tenants` + `merchants` + `tenant_subscriptions` + `tenant_packages`。 + +## P1(增强) + +1. **套餐与配额** + - 门店数、账号数、存储、短信、订单额度(上限/已用)。 + - 数据源:`tenant_packages` + `tenant_quota_usages`。 +2. **账单与支付** + - 最近账单、应付/实付、支付状态、支付方式、支付时间。 + - 数据源:`tenant_billing_statements` + `tenant_payments`。 +3. **资质状态摘要** + - 资质类型、审核状态、到期提醒。 + - 数据源:`merchant_documents`。 + +## P2(后续) + +1. **消息中心**:通知、公告、已读状态。 +2. **我的操作日志**:只看当前用户自身操作。 +3. **变更与审核轨迹**:用于追溯关键操作。 + +## 5. 页面信息架构建议(前端) + +个人中心建议拆 4 个标签页: + +1. `基础信息` +2. `账号安全` +3. `套餐与配额` +4. `账单与记录` + +> 首版可先渲染前两页,后两页按接口进度逐步放开。 + +## 6. 接口落地建议(给后端) + +建议补 1 个聚合接口 + 若干分页子接口,减少前端拼装成本。 + +### 6.1 聚合接口(建议优先) + +- `GET /api/tenant/v1/personal/overview` + - 返回:账号信息 + 安全信息 + 归属信息 + 套餐摘要 + 配额摘要。 + +### 6.2 子接口(按需) + +- `GET /api/tenant/v1/personal/roles` +- `GET /api/tenant/v1/personal/billing/statements` +- `GET /api/tenant/v1/personal/billing/payments` +- `GET /api/tenant/v1/personal/operations` +- `GET /api/tenant/v1/personal/notifications` + +## 7. 安全与展示口径 + +1. 手机号、邮箱建议脱敏展示(如 `135****5407`)。 +2. 严禁返回密码哈希、完整敏感审计参数。 +3. 日志类接口默认仅返回“当前登录用户可见范围”。 +4. 所有 ID 在前端按字符串处理,避免 long 精度问题。 + +## 8. 推荐实施顺序 + +1. 后端先给 `personal/overview`。 +2. 前端完成“基础信息 + 账号安全”可用页。 +3. 再接“套餐配额”和“账单记录”分页。 +4. 最后补“消息与操作轨迹”。 + +--- + +如确认本草案,可继续输出: + +- 字段级 DTO 草案(请求/响应)。 +- 页面原型字段清单(每个卡片显示哪些字段)。 +- 前后端任务拆分(按 P0/P1/P2)。 diff --git a/specs/001-personal-center-api/checklists/requirements.md b/specs/001-personal-center-api/checklists/requirements.md new file mode 100644 index 0000000..5832784 --- /dev/null +++ b/specs/001-personal-center-api/checklists/requirements.md @@ -0,0 +1,35 @@ +# Specification Quality Checklist: 租户个人中心 API(第一版) + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-02-09 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- Validation pass (iteration 1): all checklist items satisfied. +- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan` diff --git a/specs/001-personal-center-api/contracts/openapi.yaml b/specs/001-personal-center-api/contracts/openapi.yaml new file mode 100644 index 0000000..f7cad6c --- /dev/null +++ b/specs/001-personal-center-api/contracts/openapi.yaml @@ -0,0 +1,749 @@ +openapi: 3.0.3 +info: + title: TakeoutSaaS Tenant Personal Center API + version: 1.0.0 + description: | + 租户端个人中心查询接口契约(首版)。 + 包含总览、角色权限、套餐配额、账单、支付、操作记录、消息摘要与可见角色配置。 +servers: + - url: https://api-tenant-dev.laosankeji.com + description: Tenant API (dev) +security: + - bearerAuth: [] +tags: + - name: Personal + description: 个人中心 +paths: + /api/tenant/v1/personal/overview: + get: + tags: [Personal] + summary: 获取个人中心总览 + description: | + 聚合返回账号、安全、角色、归属、配额等信息。 + 当部分子模块失败时,返回可用数据并通过 moduleStatuses 标识失败模块与原因。 + operationId: getPersonalOverview + responses: + '200': + description: 查询成功(包含完整或部分降级数据) + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponsePersonalOverview' + '401': + $ref: '#/components/responses/Unauthorized' + + /api/tenant/v1/personal/roles: + get: + tags: [Personal] + summary: 获取我的角色与权限概览 + operationId: getPersonalRoles + responses: + '200': + description: 查询成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseRolePermissionSummary' + '401': + $ref: '#/components/responses/Unauthorized' + + /api/tenant/v1/personal/quota: + get: + tags: [Personal] + summary: 获取套餐与配额摘要 + description: 仅租户可见角色清单中的角色可访问。 + operationId: getPersonalQuota + responses: + '200': + description: 查询成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseQuotaUsageSummary' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /api/tenant/v1/personal/billing/statements: + get: + tags: [Personal] + summary: 分页查询账单记录 + description: | + 未传 from/to 时默认查询最近 90 天。 + 仅租户可见角色清单中的角色可访问。 + operationId: getBillingStatements + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/FromTime' + - $ref: '#/components/parameters/ToTime' + responses: + '200': + description: 查询成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponsePagedBillingStatements' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '422': + $ref: '#/components/responses/ValidationError' + + /api/tenant/v1/personal/billing/payments: + get: + tags: [Personal] + summary: 分页查询支付记录 + description: | + 未传 from/to 时默认查询最近 90 天。 + 仅租户可见角色清单中的角色可访问。 + operationId: getBillingPayments + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/FromTime' + - $ref: '#/components/parameters/ToTime' + responses: + '200': + description: 查询成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponsePagedPaymentRecords' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '422': + $ref: '#/components/responses/ValidationError' + + /api/tenant/v1/personal/operations: + get: + tags: [Personal] + summary: 分页查询个人操作记录 + description: | + 仅返回当前登录用户可见范围。 + 未传 from/to 时默认查询最近 90 天。 + 默认 pageSize=50,且 pageSize 最大 50。 + operationId: getPersonalOperations + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/OperationPageSize' + - $ref: '#/components/parameters/FromTime' + - $ref: '#/components/parameters/ToTime' + responses: + '200': + description: 查询成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponsePagedOperationLogs' + '401': + $ref: '#/components/responses/Unauthorized' + '422': + $ref: '#/components/responses/ValidationError' + + /api/tenant/v1/personal/notifications: + get: + tags: [Personal] + summary: 分页查询消息摘要 + operationId: getPersonalNotifications + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/UnreadOnly' + responses: + '200': + description: 查询成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponsePagedNotifications' + '401': + $ref: '#/components/responses/Unauthorized' + + /api/tenant/v1/personal/visibility/roles: + get: + tags: [Personal] + summary: 获取账单/配额可见角色清单配置 + operationId: getVisibilityRoleConfig + responses: + '200': + description: 查询成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseVisibilityRoleConfig' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + put: + tags: [Personal] + summary: 更新账单/配额可见角色清单配置 + operationId: updateVisibilityRoleConfig + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VisibilityRoleConfigUpdateRequest' + responses: + '200': + description: 更新成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseVisibilityRoleConfig' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '422': + $ref: '#/components/responses/ValidationError' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + parameters: + Page: + name: page + in: query + required: false + schema: + type: integer + minimum: 1 + default: 1 + description: 页码,从 1 开始。 + PageSize: + name: pageSize + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 50 + default: 20 + description: 每页条数,最大 50。 + OperationPageSize: + name: pageSize + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 50 + default: 50 + description: 操作记录每页条数,默认 50 且最大 50。 + FromTime: + name: from + in: query + required: false + schema: + type: string + format: date-time + description: 起始时间(UTC);不传则使用默认 90 天窗口,显式范围最大 365 天。 + ToTime: + name: to + in: query + required: false + schema: + type: string + format: date-time + description: 截止时间(UTC);不传则使用当前时间,显式范围最大 365 天。 + UnreadOnly: + name: unreadOnly + in: query + required: false + schema: + type: boolean + default: false + description: 是否仅返回未读消息。 + + responses: + Unauthorized: + description: 未认证或 Token 无效 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + Forbidden: + description: 当前角色无访问权限 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + ValidationError: + description: 参数校验失败 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + schemas: + ApiResponseBase: + type: object + required: [success, code, message] + properties: + success: + type: boolean + code: + type: integer + message: + type: string + + ApiResponseError: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + properties: + data: + nullable: true + type: object + + ApiResponsePersonalOverview: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/PersonalOverview' + + ApiResponseRolePermissionSummary: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/RolePermissionSummary' + + ApiResponseQuotaUsageSummary: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/QuotaUsageSummary' + + ApiResponsePagedBillingStatements: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/PagedBillingStatements' + + ApiResponsePagedPaymentRecords: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/PagedPaymentRecords' + + ApiResponsePagedOperationLogs: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/PagedOperationLogs' + + ApiResponsePagedNotifications: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/PagedNotifications' + + ApiResponseVisibilityRoleConfig: + allOf: + - $ref: '#/components/schemas/ApiResponseBase' + - type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/VisibilityRoleConfig' + + PersonalOverview: + type: object + required: [requestId, overallStatus, moduleStatuses] + properties: + requestId: + type: string + overallStatus: + type: string + enum: [success, partial_success, failure] + accountProfile: + $ref: '#/components/schemas/AccountProfile' + securitySnapshot: + $ref: '#/components/schemas/SecuritySnapshot' + roleSummary: + $ref: '#/components/schemas/RolePermissionSummary' + tenantAffiliation: + $ref: '#/components/schemas/TenantAffiliation' + quotaSummary: + $ref: '#/components/schemas/QuotaUsageSummary' + moduleStatuses: + type: array + items: + $ref: '#/components/schemas/ModuleStatus' + + AccountProfile: + type: object + required: [userId, account, displayName, registeredAt] + properties: + userId: + type: string + account: + type: string + displayName: + type: string + avatarUrl: + type: string + nullable: true + phoneMasked: + type: string + nullable: true + example: 135****5407 + emailMasked: + type: string + nullable: true + example: a***z@example.com + registeredAt: + type: string + format: date-time + + SecuritySnapshot: + type: object + required: [failedLoginCount, isLocked, isForceChangePassword] + properties: + lastLoginAt: + type: string + format: date-time + nullable: true + failedLoginCount: + type: integer + isLocked: + type: boolean + lockedUntil: + type: string + format: date-time + nullable: true + isForceChangePassword: + type: boolean + + RolePermissionSummary: + type: object + required: [roles, permissionCount] + properties: + roles: + type: array + items: + $ref: '#/components/schemas/RoleItem' + permissionCount: + type: integer + minimum: 0 + + RoleItem: + type: object + required: [roleCode, roleName] + properties: + roleCode: + type: string + roleName: + type: string + + TenantAffiliation: + type: object + required: [tenantId, tenantName, merchantId, merchantName, merchantStatus] + properties: + tenantId: + type: string + tenantName: + type: string + merchantId: + type: string + merchantName: + type: string + merchantStatus: + type: string + packageName: + type: string + nullable: true + subscriptionExpireAt: + type: string + format: date-time + nullable: true + + QuotaUsageSummary: + type: object + required: [items] + properties: + items: + type: array + items: + $ref: '#/components/schemas/QuotaUsageItem' + + QuotaUsageItem: + type: object + required: [quotaCode, quotaName, limitValue, usedValue, unit, usageRatio] + properties: + quotaCode: + type: string + quotaName: + type: string + limitValue: + type: number + format: decimal + usedValue: + type: number + format: decimal + unit: + type: string + usageRatio: + type: number + format: decimal + + BillingStatement: + type: object + required: [statementId, billingPeriodStart, billingPeriodEnd, amountDue, amountPaid, status, dueAt] + properties: + statementId: + type: string + billingPeriodStart: + type: string + format: date + billingPeriodEnd: + type: string + format: date + amountDue: + type: number + format: decimal + amountPaid: + type: number + format: decimal + status: + type: string + enum: [pending, partial_paid, paid, overdue, cancelled] + dueAt: + type: string + format: date-time + + PaymentRecord: + type: object + required: [paymentId, statementId, paidAmount, paymentMethod, paymentStatus] + properties: + paymentId: + type: string + statementId: + type: string + paidAmount: + type: number + format: decimal + paymentMethod: + type: string + enum: [online, bank_transfer, other] + paymentStatus: + type: string + enum: [pending, success, failed, refunded] + paidAt: + type: string + format: date-time + nullable: true + + PersonalOperationLog: + type: object + required: [operationId, operatorUserId, actionType, targetType, isSuccess, occurredAt] + properties: + operationId: + type: string + operatorUserId: + type: string + actionType: + type: string + targetType: + type: string + targetId: + type: string + nullable: true + isSuccess: + type: boolean + occurredAt: + type: string + format: date-time + + PersonalNotification: + type: object + required: [notificationId, title, category, isRead, sentAt] + properties: + notificationId: + type: string + title: + type: string + category: + type: string + isRead: + type: boolean + sentAt: + type: string + format: date-time + + VisibilityRoleConfig: + type: object + required: [tenantId, quotaVisibleRoleCodes, billingVisibleRoleCodes, updatedBy, updatedAt] + properties: + tenantId: + type: string + quotaVisibleRoleCodes: + type: array + uniqueItems: true + items: + type: string + billingVisibleRoleCodes: + type: array + uniqueItems: true + items: + type: string + updatedBy: + type: string + updatedAt: + type: string + format: date-time + + VisibilityRoleConfigUpdateRequest: + type: object + required: [quotaVisibleRoleCodes, billingVisibleRoleCodes] + properties: + quotaVisibleRoleCodes: + type: array + uniqueItems: true + minItems: 1 + items: + type: string + billingVisibleRoleCodes: + type: array + uniqueItems: true + minItems: 1 + items: + type: string + + ModuleStatus: + type: object + required: [module, status] + properties: + module: + type: string + example: billingSummary + status: + $ref: '#/components/schemas/ModuleStatusCode' + errorCode: + type: string + nullable: true + errorMessage: + type: string + nullable: true + traceId: + type: string + nullable: true + + PagedBillingStatements: + allOf: + - $ref: '#/components/schemas/PagedResultBase' + - type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/BillingStatement' + + PagedPaymentRecords: + allOf: + - $ref: '#/components/schemas/PagedResultBase' + - type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/PaymentRecord' + + PagedOperationLogs: + allOf: + - $ref: '#/components/schemas/PagedResultBase' + - type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/PersonalOperationLog' + + PagedNotifications: + allOf: + - $ref: '#/components/schemas/PagedResultBase' + - type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/PersonalNotification' + + PagedResultBase: + type: object + required: [page, pageSize, total, totalPages, items] + properties: + page: + type: integer + minimum: 1 + pageSize: + type: integer + minimum: 1 + total: + type: integer + minimum: 0 + totalPages: + type: integer + minimum: 0 + items: + type: array + items: + type: object + + ModuleStatusCode: + type: string + enum: [ok, degraded, failed, timeout, skipped] + + PersonalPagedQuery: + type: object + required: [page, pageSize] + properties: + page: + type: integer + minimum: 1 + default: 1 + pageSize: + type: integer + minimum: 1 + maximum: 50 + default: 20 + from: + type: string + format: date-time + nullable: true + to: + type: string + format: date-time + nullable: true diff --git a/specs/001-personal-center-api/data-model.md b/specs/001-personal-center-api/data-model.md new file mode 100644 index 0000000..fbb8310 --- /dev/null +++ b/specs/001-personal-center-api/data-model.md @@ -0,0 +1,191 @@ +# Data Model - 租户个人中心 API(第一版) + +## 1. Entity Overview + +本特性为查询型聚合能力,核心是将多个已有业务实体以“个人中心视图模型”方式输出。 + +## 2. Entities + +### 2.1 PersonalOverview + +- **Description**: 个人中心总览聚合结果。 +- **Fields**: + - `requestId` (string): 请求追踪标识。 + - `overallStatus` (enum): `success | partial_success | failure`。 + - `accountProfile` (AccountProfile | null) + - `securitySnapshot` (SecuritySnapshot | null) + - `roleSummary` (RolePermissionSummary | null) + - `tenantAffiliation` (TenantAffiliation | null) + - `quotaSummary` (QuotaUsageSummary | null) + - `moduleStatuses` (ModuleStatus[]): 子模块执行状态集合。 + +### 2.2 AccountProfile + +- **Description**: 当前登录用户账号基础信息。 +- **Fields**: + - `userId` (string) + - `account` (string) + - `displayName` (string) + - `avatarUrl` (string | null) + - `phoneMasked` (string | null) + - `emailMasked` (string | null) + - `registeredAt` (datetime) + +### 2.3 SecuritySnapshot + +- **Description**: 账号安全状态快照。 +- **Fields**: + - `lastLoginAt` (datetime | null) + - `failedLoginCount` (int) + - `isLocked` (bool) + - `lockedUntil` (datetime | null) + - `isForceChangePassword` (bool) + +### 2.4 RolePermissionSummary + +- **Description**: 当前用户角色与权限概览。 +- **Fields**: + - `roles` (RoleItem[]) + - `permissionCount` (int) +- **RoleItem**: + - `roleCode` (string) + - `roleName` (string) + +### 2.5 TenantAffiliation + +- **Description**: 用户归属与订阅信息。 +- **Fields**: + - `tenantId` (string) + - `tenantName` (string) + - `merchantId` (string) + - `merchantName` (string) + - `merchantStatus` (string) + - `packageName` (string | null) + - `subscriptionExpireAt` (datetime | null) + +### 2.6 QuotaUsageSummary + +- **Description**: 配额使用摘要。 +- **Fields**: + - `items` (QuotaUsageItem[]) +- **QuotaUsageItem**: + - `quotaCode` (string) + - `quotaName` (string) + - `limitValue` (decimal) + - `usedValue` (decimal) + - `unit` (string) + - `usageRatio` (decimal) + +### 2.7 BillingStatement + +- **Description**: 账单记录。 +- **Fields**: + - `statementId` (string) + - `billingPeriodStart` (date) + - `billingPeriodEnd` (date) + - `amountDue` (decimal) + - `amountPaid` (decimal) + - `status` (enum: `pending | partial_paid | paid | overdue | cancelled`) + - `dueAt` (datetime) + +### 2.8 PaymentRecord + +- **Description**: 支付流水记录。 +- **Fields**: + - `paymentId` (string) + - `statementId` (string) + - `paidAmount` (decimal) + - `paymentMethod` (enum: `wechat | alipay | bank_transfer | offline`) + - `paymentStatus` (enum: `initiated | success | failed | refunded`) + - `paidAt` (datetime | null) + +### 2.9 PersonalOperationLog + +- **Description**: 当前用户操作记录。 +- **Fields**: + - `operationId` (string) + - `operatorUserId` (string) + - `actionType` (string) + - `targetType` (string) + - `targetId` (string | null) + - `isSuccess` (bool) + - `occurredAt` (datetime) + +### 2.10 PersonalNotification + +- **Description**: 当前用户消息摘要。 +- **Fields**: + - `notificationId` (string) + - `title` (string) + - `category` (string) + - `isRead` (bool) + - `sentAt` (datetime) + +### 2.11 TenantVisibilityRoleRule + +- **Description**: 租户级账单/配额可见角色规则。 +- **Fields**: + - `tenantId` (string) [unique] + - `quotaVisibleRoleCodes` (string[]) + - `billingVisibleRoleCodes` (string[]) + - `updatedBy` (string) + - `updatedAt` (datetime) + +### 2.12 ModuleStatus + +- **Description**: 聚合子模块执行状态。 +- **Fields**: + - `module` (string) + - `status` (enum: `ok | degraded | failed | timeout | skipped`) + - `errorCode` (string | null) + - `errorMessage` (string | null) + - `traceId` (string | null) + +## 3. Relationships + +- `PersonalOverview` 1:1 聚合 `AccountProfile`、`SecuritySnapshot`、`RolePermissionSummary`、`TenantAffiliation`。 +- `PersonalOverview` 1:1 可选聚合 `QuotaUsageSummary`(可能因权限或降级为空)。 +- `BillingStatement` 1:N `PaymentRecord`(通过 `statementId` 关联)。 +- `TenantVisibilityRoleRule` 按 `tenantId` 管控 `QuotaUsageSummary` 与 `BillingStatement/PaymentRecord` 的可见性。 +- `PersonalOperationLog`、`PersonalNotification` 与当前登录用户 `userId` 强绑定,不允许跨用户读取。 + +## 4. Identity & Uniqueness Rules + +- 所有业务主键在 API 层按 `string` 传输(Snowflake ID)。 +- `TenantVisibilityRoleRule.tenantId` 全局唯一(每租户一份有效规则)。 +- 可见角色清单中的 `roleCode` 必须去重,且必须属于当前租户角色集合。 + +## 5. Validation Rules + +- 列表查询参数:`page >= 1`。 +- 通用 `pageSize`:`1..50`。 +- 操作记录查询:默认 `pageSize=50`,且最大 `50`。 +- 未传 `from/to` 时默认查询最近 `90` 天。 +- 显式时间窗最大跨度 `<= 365` 天;超限返回校验错误。 +- 手机号、邮箱只输出脱敏值,原始值不得出现在响应体。 +- 账单/配额查询需命中租户可见角色清单,否则返回权限不足。 + +## 6. Lifecycle / State Transitions + +### 6.1 模块执行状态(请求内) + +- 初始:`ok`(执行成功) +- 异常分支:`degraded` / `failed` / `timeout` / `skipped` +- 汇总规则:任一模块非 `ok` 时,`overallStatus=partial_success`;全部失败且无可用数据时 `overallStatus=failure`。 + +### 6.2 账单状态(只读语义) + +- `pending -> partial_paid -> paid` +- `pending -> overdue` +- `pending -> cancelled` + +### 6.3 支付状态(只读语义) + +- `initiated -> success` +- `initiated -> failed` +- `success -> refunded` + +## 7. Data Volume Assumptions + +- 操作记录和支付记录为高增长数据,默认 90 天窗口 + 分页约束保障查询稳定性。 +- 总览聚合在单请求内访问多个数据源,必须提供模块级状态便于前端降级展示与运维定位。 diff --git a/specs/001-personal-center-api/plan.md b/specs/001-personal-center-api/plan.md new file mode 100644 index 0000000..e5d9b6a --- /dev/null +++ b/specs/001-personal-center-api/plan.md @@ -0,0 +1,88 @@ +# Implementation Plan: 租户个人中心 API(第一版) + +**Branch**: `001-personal-center-api` | **Date**: 2026-02-09 | **Spec**: `D:\MsuMshkCode\specs\001-personal-center-api\spec.md` +**Input**: Feature specification from `/specs/001-personal-center-api/spec.md` + +**Note**: This template is filled in by the `/speckit.plan` command. See `.opencode/command/speckit.plan.md` for the execution workflow. + +## Summary + +本特性在 `TakeoutSaaS.TenantApi` 新增租户个人中心查询能力,首版一次性交付 P0/P1/P2: +个人总览、角色权限概览、套餐配额、账单、支付、个人操作记录、消息摘要,以及账单/配额可见角色 +清单配置。技术策略采用“总览聚合 + 明细分页接口”组合,并按澄清结论实现“部分失败可降级返回, +必须显式标识失败模块与原因”。 + +## Technical Context + +**Language/Version**: C# 14 / .NET 10 (ASP.NET Core Web API) +**Primary Dependencies**: MediatR, EF Core 10, Dapper 2.1+, FluentValidation, Serilog, OpenTelemetry, Asp.Versioning +**Storage**: PostgreSQL 16+, Redis 7(可选读缓存) +**Testing**: xUnit + Moq + FluentAssertions + 契约测试 + 集成测试 +**Target Platform**: Linux 容器化服务(Tenant API) +**Project Type**: multi-project SaaS(本次主要改动 TenantApi,TenantUI 按契约联调) +**Performance Goals**: 95% 查询请求 <= 2s;请求成功率 >= 99%;操作记录单次返回上限 50 条 +**Constraints**: 强制租户隔离、手机号邮箱脱敏、90 天默认时间窗、降级返回需标识模块错误、10 分钟内可回滚 +**Scale/Scope**: 面向租户端全量发布;新增 1 个总览聚合接口、6 个明细查询接口、1 个可见角色配置接口 + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +**Pre-Research Gate Review** +- **Tenant Isolation**: PASS - 采用认证用户上下文 + 租户上下文 + 数据访问层租户过滤三重约束。 +- **Four-Project Impact**: PASS - 本次编码落点在 `TakeoutSaaS.TenantApi/`,并为 `TakeoutSaaS.TenantUI/` 提供新增契约。 +- **Contract Compatibility**: PASS - 全部为新增只读接口与配置接口,保持向后兼容,不破坏现有 `/auth/*` 与 `/merchant/info`。 +- **Security and Compliance**: PASS - 强制权限校验、敏感字段脱敏、敏感查询审计日志。 +- **Quality Gates**: PASS - 计划执行 `dotnet build`、新增契约/集成测试与回归验证。 +- **Observability and Rollback**: PASS - 输出结构化日志、OTel 指标与追踪;按功能开关支持快速回退。 + +**Post-Design Gate Review** +- **Tenant Isolation**: PASS - 在 `data-model.md` 与 `openapi.yaml` 明确租户范围和越权拒绝语义。 +- **Four-Project Impact**: PASS - `contracts/openapi.yaml` 已覆盖 TenantUI 联调所需接口与字段。 +- **Contract Compatibility**: PASS - 契约仅增加新路径与新 DTO;未修改现有路径行为。 +- **Security and Compliance**: PASS - `research.md` 固化脱敏、审计、最小权限与可见角色清单策略。 +- **Quality Gates**: PASS - `quickstart.md` 提供可执行验证步骤与回归检查顺序。 +- **Observability and Rollback**: PASS - `research.md` 定义降级可观测指标与回滚触发阈值。 + +## Project Structure + +### Documentation (this feature) + +```text +specs/001-personal-center-api/ +├── plan.md +├── research.md +├── data-model.md +├── quickstart.md +├── contracts/ +│ └── openapi.yaml +└── tasks.md +``` + +### Source Code (repository root) + +```text +TakeoutSaaS.TenantApi/ +├── src/Api/TakeoutSaaS.TenantApi/Controllers/ +├── src/Application/TakeoutSaaS.Application/ +├── src/Domain/TakeoutSaaS.Domain/ +└── src/Infrastructure/TakeoutSaaS.Infrastructure/ + +TakeoutSaaS.TenantUI/ +└── apps/web-antd/ (仅消费新增接口契约,当前阶段不在本计划内编码) + +TakeoutSaaS.AdminApi/ +└── (本特性不改动) + +TakeoutSaaS.AdminUI/ +└── (本特性不改动) +``` + +**Structure Decision**: 本特性仅在 `TakeoutSaaS.TenantApi/` 落地 API 代码,`TakeoutSaaS.TenantUI/` +通过新增契约完成联调;管理员双端保持不变,避免无关改动扩大回归面。 + +## Complexity Tracking + +> **Fill ONLY if Constitution Check has violations that must be justified** + +No constitutional violations identified for this plan. diff --git a/specs/001-personal-center-api/quickstart.md b/specs/001-personal-center-api/quickstart.md new file mode 100644 index 0000000..bb8c42c --- /dev/null +++ b/specs/001-personal-center-api/quickstart.md @@ -0,0 +1,147 @@ +# Quickstart - 租户个人中心 API(第一版) + +## 1. 前置条件 + +- 已切换到分支 `001-personal-center-api`。 +- 本地已准备 TenantApi 运行依赖(PostgreSQL、Redis、配置文件)。 +- 具备至少两个测试账号: + - 账号 A:在账单/配额可见角色清单内。 + - 账号 B:不在账单/配额可见角色清单内。 + +## 2. 构建与启动 + +```bash +dotnet build "D:/MsuMshkCode/TakeoutSaaS.TenantApi/TakeoutSaaS.sln" +dotnet run --project "D:/MsuMshkCode/TakeoutSaaS.TenantApi/src/Api/TakeoutSaaS.TenantApi/TakeoutSaaS.TenantApi.csproj" +``` + +## 3. 登录获取 Token + +```bash +curl -X POST "https://api-tenant-dev.laosankeji.com/api/tenant/v1/auth/login" \ + -H "Content-Type: application/json" \ + -d '{"account":"tenant_admin","password":"your-password"}' +``` + +保存响应中的 `data.accessToken`,后续请求使用: + +```text +Authorization: Bearer +``` + +## 4. 核心接口验证 + +### 4.1 总览接口 + +```bash +curl -X GET "https://api-tenant-dev.laosankeji.com/api/tenant/v1/personal/overview" \ + -H "Authorization: Bearer " +``` + +预期: +- 返回 `success=true`。 +- `data.overallStatus` 为 `success` 或 `partial_success`。 +- `data.accountProfile.phoneMasked`、`data.accountProfile.emailMasked` 为脱敏值。 +- 若有降级,`data.moduleStatuses` 包含失败模块与错误码。 + +### 4.2 角色权限接口 + +```bash +curl -X GET "https://api-tenant-dev.laosankeji.com/api/tenant/v1/personal/roles" \ + -H "Authorization: Bearer " +``` + +预期: +- 返回角色列表与权限数量。 + +### 4.3 配额摘要接口 + +```bash +curl -X GET "https://api-tenant-dev.laosankeji.com/api/tenant/v1/personal/quota" \ + -H "Authorization: Bearer " +``` + +预期: +- 账号 A 返回配额数据。 +- 账号 B 返回 `403`(不在可见角色清单)。 + +### 4.4 账单与支付分页接口 + +```bash +curl -X GET "https://api-tenant-dev.laosankeji.com/api/tenant/v1/personal/billing/statements?page=1&pageSize=20" \ + -H "Authorization: Bearer " + +curl -X GET "https://api-tenant-dev.laosankeji.com/api/tenant/v1/personal/billing/payments?page=1&pageSize=20" \ + -H "Authorization: Bearer " +``` + +预期: +- 未传 `from/to` 时默认按最近 90 天查询。 +- 数据按时间倒序返回。 + +### 4.5 操作记录接口 + +```bash +curl -X GET "https://api-tenant-dev.laosankeji.com/api/tenant/v1/personal/operations?page=1" \ + -H "Authorization: Bearer " +``` + +预期: +- 默认查询最近 90 天。 +- 默认 `pageSize=50`。 +- 当传入 `pageSize>50` 时,返回条数仍不超过 50。 + +### 4.6 消息摘要接口 + +```bash +curl -X GET "https://api-tenant-dev.laosankeji.com/api/tenant/v1/personal/notifications?page=1&pageSize=20" \ + -H "Authorization: Bearer " +``` + +预期: +- 无数据时返回空数组而非错误。 + +### 4.7 可见角色清单配置接口 + +```bash +curl -X GET "https://api-tenant-dev.laosankeji.com/api/tenant/v1/personal/visibility/roles" \ + -H "Authorization: Bearer " + +curl -X PUT "https://api-tenant-dev.laosankeji.com/api/tenant/v1/personal/visibility/roles" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"quotaVisibleRoleCodes":["tenant-owner","finance"],"billingVisibleRoleCodes":["tenant-owner","finance"]}' +``` + +预期: +- 更新后再次访问账单/配额接口,权限判定按新清单生效。 + +## 5. 回归检查清单 + +- 现有接口 `/api/tenant/v1/auth/profile`、`/api/tenant/v1/auth/permissions`、`/api/tenant/v1/merchant/info` 行为不变。 +- 所有新增接口均要求认证。 +- 越权访问返回 `403` 或 `401`,且不泄露其他租户数据。 +- 响应体不出现未脱敏手机号、邮箱或其他敏感字段。 + +```bash +curl -X GET "https://api-tenant-dev.laosankeji.com/api/tenant/v1/auth/profile" \ + -H "Authorization: Bearer " + +curl -X GET "https://api-tenant-dev.laosankeji.com/api/tenant/v1/auth/permissions" \ + -H "Authorization: Bearer " + +curl -X GET "https://api-tenant-dev.laosankeji.com/api/tenant/v1/merchant/info" \ + -H "Authorization: Bearer " +``` + +## 6. 可观测性检查 + +- 每个请求日志中可关联 `TraceId` / `requestId`。 +- 当模块降级时,日志与指标中可定位失败模块、错误码与耗时。 +- 新增接口失败率和耗时可在监控面板中观测。 + +## 7. 发布与回滚验证 + +- 发布后执行第 4 节和第 5 节的全部接口烟测。 +- 若出现严重故障,先回滚到上一版本,再复测 `/auth/profile`、`/merchant/info` 与 `/personal/overview`。 +- 回滚后重点确认:账单/支付接口仍遵循租户隔离与角色可见性规则。 diff --git a/specs/001-personal-center-api/research.md b/specs/001-personal-center-api/research.md new file mode 100644 index 0000000..3419d3a --- /dev/null +++ b/specs/001-personal-center-api/research.md @@ -0,0 +1,54 @@ +# Phase 0 Research - 租户个人中心 API(第一版) + +## Decision 1: 接口组织采用“总览聚合 + 明细分页”组合 + +- **Decision**: 使用 `GET /personal/overview` 提供聚合首屏数据,同时为账单、支付、操作记录、消息等提供独立分页接口。 +- **Rationale**: 聚合接口满足首屏加载效率,独立接口避免过度返回,便于分别鉴权、分页和缓存。 +- **Alternatives considered**: + - 单一超大接口一次返回全部数据:实现简单但耦合高、回归风险大。 + - 全拆分接口无聚合:调用次数多,首屏体验差。 + +## Decision 2: 聚合查询采用“部分失败降级返回” + +- **Decision**: 请求语义正确时返回可用模块数据,并在响应中显式标识失败模块、错误码与错误原因。 +- **Rationale**: 个人中心是查询场景,部分可用优于整页失败;显式失败标识可避免前端误判空数据。 +- **Alternatives considered**: + - 任一模块失败即整体失败:稳定性差,用户可用性低。 + - 静默置空失败模块:对排障与运营监控不友好。 + +## Decision 3: 时间范围与分页采用统一默认策略 + +- **Decision**: 账单/支付/操作记录在未传时间筛选时默认查询最近 90 天;操作记录单次返回默认 50 条且上限 50 条。 +- **Rationale**: 兼顾查询性能与业务可用性,满足已确认的产品澄清并降低大租户尾页查询压力。 +- **Alternatives considered**: + - 默认 30 天:可能不足以覆盖常见追溯场景。 + - 默认全量历史:高并发与高数据量下风险高。 + +## Decision 4: 账单与配额可见范围由租户角色清单配置 + +- **Decision**: 提供租户级角色清单配置能力,账单/配额查询仅对配置角色开放。 +- **Rationale**: 满足不同租户组织结构差异,遵循最小权限原则,减少硬编码角色导致的维护成本。 +- **Alternatives considered**: + - 全员可见:敏感信息暴露面过大。 + - 固定仅管理员可见:灵活性不足,无法适配租户自定义角色模型。 + +## Decision 5: 安全与审计采用“敏感查询强审计 + 脱敏输出” + +- **Decision**: 手机号、邮箱统一脱敏输出;敏感查询记录审计事件(操作者、租户、模块、结果、追踪标识)。 +- **Rationale**: 满足商用 SaaS 合规要求,支持事后追溯与安全稽核。 +- **Alternatives considered**: + - 前端脱敏:后端仍可能泄露原始敏感字段。 + - 仅失败请求审计:无法覆盖敏感数据浏览行为追踪。 + +## Decision 6: 可观测与回滚按发布门禁前置 + +- **Decision**: 对新增接口输出结构化日志与 OTel 指标/追踪;定义失败率和延迟阈值,触发功能开关回滚。 +- **Rationale**: 与宪法“可观测与可回滚”原则对齐,降低首版全量发布风险。 +- **Alternatives considered**: + - 仅记录日志无指标:无法快速发现趋势型故障。 + - 人工回滚无阈值:响应慢且容易扩大故障窗口。 + +## Research Resolution Summary + +- 技术上下文中的不确定项已消解,本阶段无 `NEEDS CLARIFICATION` 遗留项。 +- 研究结论已回写到 `plan.md`、`data-model.md` 与 `contracts/openapi.yaml` 的设计约束中。 diff --git a/specs/001-personal-center-api/spec.md b/specs/001-personal-center-api/spec.md new file mode 100644 index 0000000..8d41612 --- /dev/null +++ b/specs/001-personal-center-api/spec.md @@ -0,0 +1,178 @@ +# Feature Specification: 租户个人中心 API(第一版) + +**Feature Branch**: `001-personal-center-api` +**Created**: 2026-02-09 +**Status**: Draft +**Input**: User description: "我现在要完成这个文档里面的功能 你需要帮我负责实现api部分 D:\MsuMshkCode\personal-center-prd.md" + +## Clarifications + +### Session 2026-02-09 + +- Q: 本次 API 交付范围是否只做 P0,还是覆盖 P0-P2? → A: 选择 C,首版同批交付 P0 + P1 + P2 全部查询能力。 +- Q: 聚合查询在部分子数据源异常时采用何种策略? → A: 选择 B,全量降级返回可用数据并标记失败部分。 +- Q: 账单/支付/操作记录列表在未传时间筛选时默认查询范围是多少? → A: 选择 B,默认最近 90 天。 +- Q: 账单与配额数据在租户内的可见权限如何定义? → A: 选择 D,由租户自行配置可见角色清单。 +- Q: 操作记录单次请求默认返回条数上限是多少? → A: 选择 B,默认上限 50 条。 + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - 查询个人中心总览 (Priority: P1) + +作为租户端已登录用户,我希望一次性看到个人账号、安全状态、角色权限概览和归属信息, +以便快速确认账号可用性与租户/商户状态。 + +**Why this priority**: 这是个人中心首屏价值,缺失会导致页面无法可用。 + +**Independent Test**: 使用有效租户账号请求总览,返回完整字段且敏感信息脱敏, +不依赖后续账单或日志接口即可单独上线。 + +**Acceptance Scenarios**: + +1. **Given** 用户已登录且账号正常,**When** 请求个人中心总览, + **Then** 返回账号信息、安全信息、角色权限概览和归属信息。 +2. **Given** 用户手机号或邮箱存在,**When** 请求个人中心总览, + **Then** 返回脱敏后的展示值而不是完整敏感数据。 +3. **Given** 用户尝试访问非本租户数据,**When** 发起总览查询, + **Then** 系统拒绝请求且不返回任何他租户信息。 + +--- + +### User Story 2 - 查询套餐配额与账单支付 (Priority: P2) + +作为租户端运营人员,我希望查看套餐额度使用和近期账单支付记录, +以便判断续费风险和经营成本。 + +**Why this priority**: 该能力直接影响续费、配额预警和日常财务决策。 + +**Independent Test**: 在不改动总览接口的情况下,单独查询套餐配额与账单支付列表, +能够独立完成分页浏览和状态判断。 + +**Acceptance Scenarios**: + +1. **Given** 租户已有套餐与配额,**When** 查询套餐配额摘要, + **Then** 返回各配额项上限、已用量和到期相关信息。 +2. **Given** 租户存在历史账单与支付记录,**When** 按分页查询账单与支付, + **Then** 返回按时间倒序的数据并包含核心状态信息。 +3. **Given** 用户请求超出本租户范围的账单数据,**When** 发起查询, + **Then** 系统拒绝并记录安全审计事件。 +4. **Given** 用户未传时间筛选条件,**When** 查询账单或支付记录, + **Then** 系统默认返回最近 90 天的数据。 +5. **Given** 用户角色未包含在租户配置的可见角色清单中,**When** 查询账单或配额数据, + **Then** 系统拒绝访问并返回权限不足提示。 + +--- + +### User Story 3 - 查询个人操作记录与消息摘要 (Priority: P3) + +作为租户端用户,我希望查看自己的关键操作记录和消息摘要, +以便追溯行为并跟进待处理通知。 + +**Why this priority**: 这是增强能力,能提升可追溯性和用户自助排障效率。 + +**Independent Test**: 在不依赖前两组接口扩展的前提下,单独查询个人操作记录与消息摘要, +可以独立验证权限范围和空数据处理。 + +**Acceptance Scenarios**: + +1. **Given** 当前用户存在历史操作,**When** 查询个人操作记录, + **Then** 仅返回当前用户可见范围内的记录。 +2. **Given** 当前用户没有消息数据,**When** 查询消息摘要, + **Then** 返回空列表且不报错。 +3. **Given** 当前用户尝试读取其他用户记录,**When** 发起请求, + **Then** 系统拒绝并返回明确的权限不足提示。 +4. **Given** 用户未传时间筛选条件,**When** 查询个人操作记录, + **Then** 系统默认返回最近 90 天的数据。 +5. **Given** 用户未传分页大小或传入过大分页大小,**When** 查询个人操作记录, + **Then** 系统单次返回条数不超过 50 条。 + +--- + +### Multi-Project Impact *(mandatory)* + +- **Impacted Projects**: TenantApi(实现范围), TenantUI(契约消费与联调范围) +- **Primary Contract Surface**: 个人中心总览、角色概览、套餐配额、账单支付、操作记录、消息摘要查询接口 +- **Change Type**: Backward compatible(新增查询能力,不破坏现有登录与菜单能力) +- **Compatibility Plan**: 保留现有可用查询接口与返回口径;新增查询能力在同一版本窗口一次性开放并联调验收 +- **Rollback Plan**: 出现高风险故障时关闭新增能力入口并回退到既有个人信息查询口径 + +### Edge Cases + +- 用户账号被锁定、冻结或强制改密时,仍需返回可解释的账号安全状态。 +- 用户缺少头像、邮箱或手机号等可选资料时,接口需稳定返回可识别的空值。 +- 用户拥有多个角色且权限重叠时,权限概览需去重并保持统计一致。 +- 套餐已到期但账单仍有未结项时,账单与套餐状态需可同时展示。 +- 大量历史账单或操作记录分页到尾页时,需返回空页而不是异常。 +- 通知、操作记录等增强能力暂无数据时,需返回空集合并保证页面可正常渲染。 +- 聚合查询中某个子模块异常时,接口需返回其他可用模块,并在响应中标注降级模块与原因。 +- 操作记录查询在请求分页大小超过上限时,需按上限 50 条进行约束并保持响应成功。 + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: 系统 MUST 为已认证租户用户提供个人中心总览查询能力。 +- **FR-002**: 总览结果 MUST 包含我的账号、账号安全、角色权限概览和归属信息四类内容。 +- **FR-003**: 系统 MUST 提供我的角色与权限概览查询,至少包含角色标识和权限数量。 +- **FR-004**: 系统 MUST 提供套餐与配额摘要查询,至少包含每类配额的上限与已用量。 +- **FR-005**: 系统 MUST 提供账单列表查询,支持分页并返回核心账单状态信息。 +- **FR-006**: 系统 MUST 提供支付记录查询,支持分页并返回支付方式、支付状态和支付时间。 +- **FR-007**: 系统 MUST 提供个人操作记录查询,且仅返回当前登录用户可见范围的数据。 +- **FR-008**: 系统 MUST 提供个人消息摘要查询,并在无数据时返回空集合。 +- **FR-009**: 系统 MUST 对手机号和邮箱进行脱敏展示,禁止返回完整敏感值。 +- **FR-010**: 系统 MUST 严禁返回密码哈希、完整敏感审计参数和其他高敏字段。 +- **FR-011**: 所有个人中心查询 MUST 执行租户隔离与用户权限校验,杜绝跨租户访问。 +- **FR-012**: 系统 MUST 为敏感查询行为记录审计事件,支持后续追踪与合规检查。 +- **FR-013**: 系统 MUST 保持现有登录、权限和商户信息相关能力不被本次变更破坏。 +- **FR-014**: 首版发布 MUST 同批交付 P0、P1、P2 所有查询能力,不拆分为多次功能发布。 +- **FR-015**: 当部分子查询失败时,系统 MUST 返回可用数据,并明确标记失败模块及失败原因,禁止无提示静默置空。 +- **FR-016**: 账单、支付、操作记录查询在未显式传入时间条件时 MUST 默认返回最近 90 天数据,并支持自定义时间范围查询。 +- **FR-017**: 账单与配额相关查询 MUST 支持租户级可见角色清单配置,仅配置内角色可访问对应数据。 +- **FR-018**: 个人操作记录查询 MUST 在未指定分页大小时默认返回 50 条,且单次请求返回上限不得超过 50 条。 + +### Key Entities *(include if feature involves data)* + +- **PersonalOverview**: 个人中心总览聚合对象,承载账号、安全、角色和归属信息。 +- **AccountProfile**: 账号基础信息对象,包含账号标识、昵称、头像、联系方式、注册时间。 +- **SecuritySnapshot**: 账号安全状态对象,包含最近登录、失败次数、锁定状态、改密要求。 +- **RolePermissionSummary**: 角色权限概览对象,包含角色集合与权限数量统计。 +- **TenantAffiliation**: 归属信息对象,包含租户名称、商户名称、商户状态、套餐到期信息。 +- **QuotaUsageSummary**: 配额摘要对象,包含各配额项的上限、已用量与预警状态。 +- **BillingStatement**: 账单对象,包含账单周期、应付/实付金额、账单状态。 +- **PaymentRecord**: 支付记录对象,包含支付方式、支付状态、支付时间与关联账单标识。 +- **PersonalOperationLog**: 个人操作记录对象,包含操作类型、对象、结果和时间。 +- **PersonalNotification**: 个人消息对象,包含消息标题、类型、发送时间与已读状态。 + +### Non-Functional Requirements *(mandatory)* + +- **NFR-001**: 在正常业务负载下,95% 的个人中心查询请求应在 2 秒内完成。 +- **NFR-002**: 个人中心查询能力月度可用性应不低于 99.9%。 +- **NFR-003**: 核心查询能力(总览、账单、配额)在发布后必须具备可监控性,且可识别失败原因。 +- **NFR-004**: 所有响应必须符合敏感信息脱敏与最小暴露原则。 +- **NFR-005**: 发生发布故障时,新增能力必须可在 10 分钟内回退至稳定状态。 +- **NFR-006**: 当出现部分降级时,95% 的请求应在 2 秒内返回可用数据并附带降级标识。 + +### Scope Boundaries + +- 本期发布目标:P0、P1、P2 查询能力一次性交付并完成联调验收。 +- 本期包含:个人中心查询类能力(总览、角色概览、套餐配额、账单支付、操作记录、消息摘要)。 +- 本期不包含:个人资料编辑、密码修改、头像上传、通知发送策略改造。 +- 本期不包含:管理员端个人中心改版与跨端统一消息中心建设。 + +### Assumptions & Dependencies + +- 认证体系已能识别当前登录用户和所属租户。 +- 租户、商户、账单、支付、日志等数据源可在本期提供稳定查询能力。 +- 租户端前端将按新增契约在同一版本窗口完成接入与联调,不采用分阶段隐藏标签策略。 +- 消息数据在首版可能为空,空集合被视为有效业务响应。 +- 每个租户可维护并生效一份账单/配额可见角色清单,用于查询权限判定。 + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: 试点租户中,至少 95% 的用户可在首次访问时成功看到个人中心核心信息。 +- **SC-002**: 上线后跨租户越权读取事件为 0,且抽检中无敏感字段泄露。 +- **SC-003**: 账单与配额查询场景下,至少 95% 的用户可在 3 秒内获得可用结果。 +- **SC-004**: 上线 30 天内,与“找不到个人账号/安全/套餐信息”相关的工单数量下降 50%。 +- **SC-005**: 首版发布后,个人中心相关请求成功率保持在 99% 及以上(无效认证请求除外)。 diff --git a/specs/001-personal-center-api/tasks.md b/specs/001-personal-center-api/tasks.md new file mode 100644 index 0000000..0ed379d --- /dev/null +++ b/specs/001-personal-center-api/tasks.md @@ -0,0 +1,236 @@ +--- + +description: "Task list for tenant personal center API implementation" +--- + +# Tasks: 租户个人中心 API(第一版) + +**Input**: Design documents from `/specs/001-personal-center-api/` +**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md, contracts/, quickstart.md + +**Tests**: 规格未显式要求新增自动化测试代码,本任务清单以可独立验收与回归验证为准。 + +**Organization**: Tasks are grouped by user story so each story can be implemented and verified independently. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependency on incomplete tasks) +- **[Story]**: User story label (`[US1]`, `[US2]`, `[US3]`) +- Every task includes an explicit file path + +## Path Conventions + +- Tenant API: `TakeoutSaaS.TenantApi/src/` +- Feature docs: `specs/001-personal-center-api/` + +## Phase 1: Setup (Shared Infrastructure) + +**Purpose**: Create the personal-center implementation skeleton shared by all stories. + +- [X] T001 Create Personal feature directory scaffold in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/` +- [X] T002 Create Personal API controller scaffold in `TakeoutSaaS.TenantApi/src/Api/TakeoutSaaS.TenantApi/Controllers/PersonalController.cs` +- [X] T003 [P] Create module status DTO scaffold in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Dto/PersonalModuleStatusDto.cs` +- [X] T004 [P] Create common paged query contract in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Dto/PersonalPagedQuery.cs` + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Shared building blocks that MUST be ready before user-story implementation. + +**CRITICAL**: No user story work starts until this phase is complete. + +- [X] T005 Create personal request context service in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Services/PersonalContextService.cs` +- [X] T006 [P] Create sensitive data masking service in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Services/PersonalMaskingService.cs` +- [X] T007 [P] Create common date-range validator in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Validators/PersonalDateRangeValidator.cs` +- [X] T008 Create module status builder service in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Services/PersonalModuleStatusService.cs` +- [X] T009 Update personal controller base route/auth/produces metadata in `TakeoutSaaS.TenantApi/src/Api/TakeoutSaaS.TenantApi/Controllers/PersonalController.cs` +- [X] T010 Update shared personal schemas and parameters in `specs/001-personal-center-api/contracts/openapi.yaml` +- [X] T011 Create personal mapping bootstrap in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/PersonalMapping.cs` + +**Checkpoint**: Foundation complete - user stories can proceed. + +--- + +## Phase 3: User Story 1 - 查询个人中心总览 (Priority: P1) 🎯 MVP + +**Goal**: Deliver overview + role summary APIs with masking and module-status semantics. + +**Independent Test**: 使用有效租户账号调用 `/personal/overview` 与 `/personal/roles`,返回账号/安全/角色/归属信息且手机号邮箱脱敏。 + +**Implementation Tasks** + +- [X] T012 [P] [US1] Create overview and role DTOs in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Dto/` +- [X] T013 [P] [US1] Create query contracts in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Queries/GetPersonalOverviewQuery.cs` +- [X] T014 [P] [US1] Implement role summary handler in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Handlers/GetPersonalRolesQueryHandler.cs` +- [X] T015 [P] [US1] Implement overview aggregation handler in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Handlers/GetPersonalOverviewQueryHandler.cs` +- [X] T016 [US1] Add US1 validators in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Validators/GetPersonalOverviewQueryValidator.cs` +- [X] T017 [US1] Implement `/personal/overview` and `/personal/roles` actions in `TakeoutSaaS.TenantApi/src/Api/TakeoutSaaS.TenantApi/Controllers/PersonalController.cs` +- [X] T018 [US1] Sync US1 contract schemas/examples in `specs/001-personal-center-api/contracts/openapi.yaml` +- [X] T019 [US1] Add US1 verification steps in `specs/001-personal-center-api/quickstart.md` + +**Checkpoint**: US1 independently works and is verifiable. + +--- + +## Phase 4: User Story 2 - 查询套餐配额与账单支付 (Priority: P2) + +**Goal**: Deliver quota/billing/payment APIs and tenant-configurable visibility role rules. + +**Independent Test**: 在不依赖 US3 的情况下,调用 `/personal/quota`、`/personal/billing/statements`、`/personal/billing/payments`、`/personal/visibility/roles` 并验证 90 天默认时间窗与角色可见性。 + +**Implementation Tasks** + +- [X] T020 [P] [US2] Create quota/billing/payment/visibility DTOs in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Dto/` +- [X] T021 [P] [US2] Create US2 query/command contracts in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Queries/` +- [X] T022 [US2] Add visibility rule entity in `TakeoutSaaS.TenantApi/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantVisibilityRoleRule.cs` +- [X] T023 [US2] Add visibility rule repository interface in `TakeoutSaaS.TenantApi/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantVisibilityRoleRuleRepository.cs` +- [X] T024 [US2] Add visibility rule DbSet/config in `TakeoutSaaS.TenantApi/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs` +- [X] T025 [US2] Implement visibility rule repository in `TakeoutSaaS.TenantApi/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Repositories/TenantVisibilityRoleRuleRepository.cs` +- [X] T026 [US2] Register visibility rule repository in `TakeoutSaaS.TenantApi/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs` +- [X] T027 [US2] Extend payment repository contract in `TakeoutSaaS.TenantApi/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantPaymentRepository.cs` +- [X] T028 [US2] Implement tenant-scoped paged payment search in `TakeoutSaaS.TenantApi/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Repositories/TenantPaymentRepository.cs` +- [X] T029 [P] [US2] Implement quota summary handler in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Handlers/GetPersonalQuotaQueryHandler.cs` +- [X] T030 [P] [US2] Implement billing statements handler in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Handlers/SearchPersonalBillingStatementsQueryHandler.cs` +- [X] T031 [P] [US2] Implement payments handler in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Handlers/SearchPersonalPaymentsQueryHandler.cs` +- [X] T032 [US2] Implement visibility config handlers and validator in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Handlers/` +- [X] T033 [US2] Implement US2 endpoints in `TakeoutSaaS.TenantApi/src/Api/TakeoutSaaS.TenantApi/Controllers/PersonalController.cs` +- [X] T034 [US2] Add sensitive query audit service in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Services/PersonalAuditService.cs` +- [X] T035 [US2] Sync US2 contract parameters/schemas in `specs/001-personal-center-api/contracts/openapi.yaml` +- [X] T036 [US2] Add US2 verification steps in `specs/001-personal-center-api/quickstart.md` + +**Checkpoint**: US2 independently works and is verifiable. + +--- + +## Phase 5: User Story 3 - 查询个人操作记录与消息摘要 (Priority: P3) + +**Goal**: Deliver operations/notifications APIs with operator scope and pagination defaults. + +**Independent Test**: 调用 `/personal/operations` 与 `/personal/notifications`,验证当前用户范围、90 天默认时间窗、操作记录 `pageSize<=50` 限制和空列表语义。 + +**Implementation Tasks** + +- [X] T037 [P] [US3] Create operations and notifications DTOs in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Dto/` +- [X] T038 [P] [US3] Create US3 query contracts in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Queries/` +- [X] T039 [US3] Extend operation log repository contract in `TakeoutSaaS.TenantApi/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/IOperationLogRepository.cs` +- [X] T040 [US3] Implement operator-scoped paged search in `TakeoutSaaS.TenantApi/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Repositories/EfOperationLogRepository.cs` +- [X] T041 [P] [US3] Implement operations handler in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Handlers/SearchPersonalOperationsQueryHandler.cs` +- [X] T042 [P] [US3] Implement notifications handler in `TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Handlers/SearchPersonalNotificationsQueryHandler.cs` +- [X] T043 [US3] Implement US3 endpoints in `TakeoutSaaS.TenantApi/src/Api/TakeoutSaaS.TenantApi/Controllers/PersonalController.cs` +- [X] T044 [US3] Sync US3 contract schemas/limits in `specs/001-personal-center-api/contracts/openapi.yaml` +- [X] T045 [US3] Add US3 verification steps in `specs/001-personal-center-api/quickstart.md` + +**Checkpoint**: US3 independently works and is verifiable. + +--- + +## Phase 6: Polish & Cross-Cutting Concerns + +**Purpose**: Final hardening, verification, and release readiness. + +- [X] T046 [P] Update XML comments and Swagger annotations in `TakeoutSaaS.TenantApi/src/Api/TakeoutSaaS.TenantApi/Controllers/PersonalController.cs` +- [X] T047 [P] Update release/rollback verification steps in `specs/001-personal-center-api/quickstart.md` +- [X] T048 Run solution build and endpoint smoke checks using `TakeoutSaaS.TenantApi/TakeoutSaaS.sln` +- [X] T049 Validate no-regression behavior in `TakeoutSaaS.TenantApi/src/Api/TakeoutSaaS.TenantApi/Controllers/AuthController.cs` +- [X] T050 Validate no-regression behavior in `TakeoutSaaS.TenantApi/src/Api/TakeoutSaaS.TenantApi/Controllers/MerchantController.cs` + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Phase 1 (Setup)**: Start immediately +- **Phase 2 (Foundational)**: Depends on Phase 1 completion; blocks all user stories +- **Phase 3-5 (User Stories)**: Depend on Phase 2 completion +- **Phase 6 (Polish)**: Depends on all selected user stories completed + +### User Story Dependencies + +- **US1 (P1)**: No dependency on US2/US3; recommended MVP +- **US2 (P2)**: Depends only on foundational phase; independent from US3 business flow +- **US3 (P3)**: Depends only on foundational phase; independent from US2 business flow + +### Recommended Delivery Order + +1. US1 (MVP) +2. US2 +3. US3 + +### Dependency Graph + +```text +Setup -> Foundational -> {US1, US2, US3} -> Polish + \-> MVP = US1 +``` + +--- + +## Parallel Opportunities + +- Setup: `T003`, `T004` +- Foundational: `T006`, `T007` +- US1: `T012`, `T013`, `T014`, `T015` +- US2: `T020`, `T021`, `T029`, `T030`, `T031` +- US3: `T037`, `T038`, `T041`, `T042` +- Polish: `T046`, `T047` + +--- + +## Parallel Example: User Story 1 + +```bash +# Parallel DTO/query scaffolding +Task: "T012 [US1] DTOs in TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Dto/" +Task: "T013 [US1] Queries in TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Queries/" + +# Parallel handler implementation after scaffolding +Task: "T014 [US1] GetPersonalRolesQueryHandler.cs" +Task: "T015 [US1] GetPersonalOverviewQueryHandler.cs" +``` + +## Parallel Example: User Story 2 + +```bash +# Parallel model/contract and independent handlers +Task: "T020 [US2] DTOs in TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Dto/" +Task: "T021 [US2] Query/Command contracts in TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Queries/" +Task: "T029 [US2] GetPersonalQuotaQueryHandler.cs" +Task: "T030 [US2] SearchPersonalBillingStatementsQueryHandler.cs" +Task: "T031 [US2] SearchPersonalPaymentsQueryHandler.cs" +``` + +## Parallel Example: User Story 3 + +```bash +# Parallel contracts and handlers +Task: "T037 [US3] DTOs in TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Dto/" +Task: "T038 [US3] Queries in TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Personal/Queries/" +Task: "T041 [US3] SearchPersonalOperationsQueryHandler.cs" +Task: "T042 [US3] SearchPersonalNotificationsQueryHandler.cs" +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 Only) + +1. Complete Phase 1 and Phase 2 +2. Deliver US1 (Phase 3) +3. Validate US1 via quickstart and smoke checks +4. Demo MVP before expanding scope + +### Incremental Delivery + +1. Foundation ready +2. Deliver US1 and verify +3. Deliver US2 and verify role-based visibility + billing flows +4. Deliver US3 and verify operations/notifications flows +5. Execute Phase 6 hardening and release readiness checks + +### Validation Notes + +- Each story has independent acceptance verification steps bound to API paths in `specs/001-personal-center-api/quickstart.md`. +- All tasks follow strict checklist format: checkbox + ID + optional `[P]` + required `[USx]` in story phases + explicit file path.