feat: 实现完整的多租户公告管理系统

核心功能:
- 公告状态机(草稿/已发布/已撤销)支持发布、撤销和重新发布
- 发布者范围区分平台级和租户级公告
- 目标受众定向推送(全部租户/指定角色/指定用户)
- 平台管理、租户管理和应用端查询API
- 已读/未读管理和未读统计

技术实现:
- CQRS+DDD架构,清晰的领域边界和事件驱动
- 查询性能优化:数据库端排序和限制,估算策略减少内存占用
- 并发控制:修复RowVersion配置(IsRowVersion→IsConcurrencyToken)
- 完整的FluentValidation验证器和输入保护

测试验证:
- 36个测试全部通过(27单元+9集成)
- 性能测试达标(1000条数据<5秒)
- 代码质量评级A(优秀)

文档:
- 完整的ADR、API文档和迁移指南
- 交付报告和技术债务记录
This commit is contained in:
2025-12-20 19:50:17 +08:00
parent 00eb357e6e
commit 857f776447
76 changed files with 12957 additions and 281 deletions

View File

@@ -0,0 +1,314 @@
# 公告管理 API 文档
> 最后更新日期2025-12-20
本文档覆盖公告管理相关 API包括平台公告、租户公告管理端接口以及应用端已认证用户接口。
## 统一约定
- 认证方式:`Authorization: Bearer <JWT>`
- 时间字段均为 UTCISO 8601
- 雪花 ID 以字符串形式序列化返回。
- 统一响应结构:`ApiResponse<T>`
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {},
"errors": null,
"traceId": "01JH...",
"timestamp": "2025-12-20T12:00:00Z"
}
```
分页结构:
```json
{
"items": [],
"page": 1,
"pageSize": 20,
"totalCount": 0,
"totalPages": 0
}
```
## 关键枚举与字段
- `AnnouncementStatus``Draft(0)``Published(1)``Revoked(2)`
- `TenantAnnouncementType``System(0)``Billing(1)``Operation(2)``SYSTEM_PLATFORM_UPDATE(3)``SYSTEM_SECURITY_NOTICE(4)``SYSTEM_COMPLIANCE(5)``TENANT_INTERNAL(6)``TENANT_FINANCE(7)``TENANT_OPERATION(8)`
- `PublisherScope``Platform(0)``Tenant(1)`(只读字段)
- `RowVersion`并发控制字段Base64 字符串)。
## 目标受众TargetType / TargetParameters
系统使用 `TargetType`(不区分大小写)+ `TargetParameters(JSON)` 过滤可见公告:
- `ALL_TENANTS`:平台全量(可带约束)
- `TENANT_ALL`:单租户全量
- `SPECIFIC_TENANTS`
- `USERS` / `SPECIFIC_USERS` / `USER_IDS`
- `ROLES` / `ROLE`
- `PERMISSIONS` / `PERMISSION`
- `MERCHANTS` / `MERCHANT_IDS`
`TargetParameters` 示例:
```json
{
"tenantIds": [100000000000000001],
"userIds": [200000000000000001],
"merchantIds": [300000000000000001],
"roles": ["OpsManager"],
"permissions": ["tenant-announcement:read"],
"departments": ["NorthRegion"]
}
```
注意:`TargetParameters` 为字符串 JSON解析失败会导致公告对该用户不可见失败即隐藏
## 数据流(示意)
```mermaid
flowchart LR
Client[客户端] --> API[API Controller]
API --> Mediator[MediatR]
Mediator --> Handler[Query/Command Handler]
Handler --> Repo[Repository]
Repo --> DB[(PostgreSQL)]
```
---
# 平台公告 API
> 路由前缀:`/api/platform/announcements`(无版本前缀)
### 1) 创建平台公告
- **方法**POST
- **路径**`/api/platform/announcements`
- **权限**`platform-announcement:create`
- **请求体**`CreateTenantAnnouncementCommand`
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**400 / 403
请求示例:
```json
{
"title": "平台升级通知",
"content": "系统将于今晚 23:00 维护。",
"announcementType": 0,
"priority": 10,
"effectiveFrom": "2025-12-20T00:00:00Z",
"effectiveTo": null,
"targetType": "all_tenants",
"targetParameters": null
}
```
响应示例:
```json
{
"success": true,
"code": 200,
"data": {
"id": "900123456789012345",
"tenantId": "0",
"title": "平台升级通知",
"status": "Draft"
}
}
```
### 2) 查询平台公告列表
- **方法**GET
- **路径**`/api/platform/announcements`
- **权限**`platform-announcement:create`
- **查询参数**
- `page` / `pageSize`
- `status`Draft/Published/Revoked
- `announcementType`
- `isActive`
- `effectiveFrom` / `effectiveTo`
- `onlyEffective`
- **响应**`ApiResponse<PagedResult<TenantAnnouncementDto>>`
- **错误码**403
示例:`GET /api/platform/announcements?page=1&pageSize=20&status=Published`
### 3) 获取平台公告详情
- **方法**GET
- **路径**`/api/platform/announcements/{announcementId}`
- **权限**`platform-announcement:create`
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**403 / 404
### 4) 更新平台公告(仅草稿)
- **方法**PUT
- **路径**`/api/platform/announcements/{announcementId}`
- **权限**`platform-announcement:create`
- **请求体**`UpdateTenantAnnouncementCommand`
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**403 / 404 / 409
请求示例:
```json
{
"title": "平台升级通知(更新)",
"content": "维护时间调整为 23:30。",
"targetType": "all_tenants",
"targetParameters": null,
"rowVersion": "AAAAAAAAB9E="
}
```
### 5) 发布平台公告
- **方法**POST
- **路径**`/api/platform/announcements/{announcementId}/publish`
- **权限**`platform-announcement:publish`
- **请求体**`PublishAnnouncementCommand`
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**403 / 404 / 409
请求示例:
```json
{ "rowVersion": "AAAAAAAAB9E=" }
```
### 6) 撤销平台公告
- **方法**POST
- **路径**`/api/platform/announcements/{announcementId}/revoke`
- **权限**`platform-announcement:revoke`
- **请求体**`RevokeAnnouncementCommand`
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**403 / 404 / 409
请求示例:
```json
{ "rowVersion": "AAAAAAAAB9E=" }
```
---
# 租户公告管理 API管理端
> 路由前缀:`/api/admin/v{version}/tenants/{tenantId}/announcements`
### 1) 查询租户公告列表
- **方法**GET
- **路径**`/api/admin/v1/tenants/{tenantId}/announcements`
- **权限**`tenant-announcement:read`
- **查询参数**
- `page` / `pageSize`
- `status` / `announcementType`
- `isActive` / `effectiveFrom` / `effectiveTo` / `onlyEffective`
- **响应**`ApiResponse<PagedResult<TenantAnnouncementDto>>`
- **错误码**403
### 2) 获取租户公告详情
- **方法**GET
- **路径**`/api/admin/v1/tenants/{tenantId}/announcements/{announcementId}`
- **权限**`tenant-announcement:read`
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**403 / 404
### 3) 创建租户公告
- **方法**POST
- **路径**`/api/admin/v1/tenants/{tenantId}/announcements`
- **权限**`tenant-announcement:create`
- **请求体**`CreateTenantAnnouncementCommand`
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**400 / 403
请求示例:
```json
{
"title": "租户公告",
"content": "新品上线提醒",
"announcementType": 0,
"priority": 5,
"effectiveFrom": "2025-12-20T00:00:00Z",
"targetType": "roles",
"targetParameters": "{\"roles\":[\"OpsManager\"]}"
}
```
### 4) 更新租户公告(仅草稿)
- **方法**PUT
- **路径**`/api/admin/v1/tenants/{tenantId}/announcements/{announcementId}`
- **权限**`tenant-announcement:update`
- **请求体**`UpdateTenantAnnouncementCommand`
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**403 / 404 / 409
### 5) 发布租户公告
- **方法**POST
- **路径**`/api/admin/v1/tenants/{tenantId}/announcements/{announcementId}/publish`
- **权限**`tenant-announcement:publish`
- **请求体**`PublishAnnouncementCommand`
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**403 / 404 / 409
### 6) 撤销租户公告
- **方法**POST
- **路径**`/api/admin/v1/tenants/{tenantId}/announcements/{announcementId}/revoke`
- **权限**`tenant-announcement:revoke`
- **请求体**`RevokeAnnouncementCommand`
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**403 / 404 / 409
### 7) 删除租户公告
- **方法**DELETE
- **路径**`/api/admin/v1/tenants/{tenantId}/announcements/{announcementId}`
- **权限**`tenant-announcement:delete`
- **响应**`ApiResponse<bool>`
- **错误码**403
### 8) 标记公告已读(兼容旧路径)
- **方法**POST
- **路径**`/api/admin/v1/tenants/{tenantId}/announcements/{announcementId}/read`
- **权限**`tenant-announcement:read`
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**403 / 404
---
# 应用端公告 API已认证用户
> 路由前缀:`/api/app/announcements`(目前挂载在 AdminApi
### 1) 获取可见公告列表
- **方法**GET
- **路径**`/api/app/announcements`
- **权限**:登录即可
- **查询参数**`page` / `pageSize`(其他筛选参数会被覆盖为已发布与有效期内)
- **响应**`ApiResponse<PagedResult<TenantAnnouncementDto>>`
- **错误码**401
### 2) 获取未读公告列表
- **方法**GET
- **路径**`/api/app/announcements/unread`
- **权限**:登录即可
- **查询参数**`page` / `pageSize`
- **响应**`ApiResponse<PagedResult<TenantAnnouncementDto>>`
- **错误码**401
### 3) 标记公告已读
- **方法**POST
- **路径**`/api/app/announcements/{announcementId}/mark-read`
- **权限**:登录即可
- **请求体**:无
- **响应**`ApiResponse<TenantAnnouncementDto>`
- **错误码**401 / 404
---
## 常见错误码
- **400**:参数验证失败
- **401**:未认证
- **403**:无权限
- **404**:公告不存在或不可见
- **409**:状态冲突(例如已发布不可编辑)
> 提示:实际错误码与消息由 `BusinessException` 和中间件统一返回。