# Bookify **Repository Path**: ymjake/bookify ## Basic Information - **Project Name**: Bookify - **Description**: Evently是一个整洁架构实践项目,与米兰Jovanovic的Bookify课程材料一致。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-08 - **Last Updated**: 2026-07-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Bookify `Bookify` 是一个基于 ASP.NET Core 10 的短租预订示例项目,主要用于实践偏务实风格的整洁架构设计。 这个项目不是为了堆砌框架概念,而是保留足够真实的业务行为来承载架构决策:外部身份认证、本地授权、预订流程、评论、领域事件、Outbox、PostgreSQL、Redis、OpenTelemetry,以及现代 Minimal API 管道。 ## 项目结构 - `src/Bookify.SharedKernel`:DDD 基础抽象、领域事件、规格模式、结果模型 - `src/Bookify.Domain`:短租领域模型、实体、值对象、定价规则、领域事件 - `src/Bookify.Application`:CQRS 用例、验证器、装饰器、应用层抽象 - `src/Bookify.Infrastructure`:EF Core、PostgreSQL、Dapper 数据初始化、Keycloak 集成、本地授权、Redis、邮件、Outbox、时间服务 - `src/Bookify.WebApi`:Minimal API、接口版本控制、Swagger、异常处理、限流、输出缓存、响应压缩、请求上下文 - `src/AppHost`:Aspire 编排,负责启动 WebApi、PostgreSQL、Redis、Seq、Keycloak - `src/ServiceDefaults`:Aspire Service Defaults、OpenTelemetry、健康检查、服务发现、弹性配置 - `tests/Bookify.Domain.UnitTests`:领域规则单元测试 - `tests/Bookify.IntegrationTests`:基于 Aspire 的接口与数据库集成测试 ## 技术栈 - Clean Architecture - Minimal APIs - API Versioning - Swagger / OpenAPI - 自定义轻量 CQRS 分发 + 装饰器 - FluentValidation - EF Core 10 + PostgreSQL - Dapper 作为轻量 SQL 辅助 - Keycloak 身份认证 - 本地数据库角色与权限模型 - Redis 分布式缓存 - Quartz + Outbox 模式 - OpenTelemetry 日志、链路、指标 - Seq 本地观测 - AspNetCore常用中间件配置 ## 业务流程 1. 用户通过 API 注册,同时创建 Keycloak 身份和本地业务用户 2. 用户在 `bookify` Realm 登录,使用 JWT 访问 API 3. 按入住和离店日期搜索可预订公寓 4. 创建预订 5. 根据当前状态确认、拒绝或取消预订 6. 入住结束后完成预订 7. 仅允许已完成入住的订单创建评论 8. 领域事件通过 Outbox 异步处理 ## 认证与授权 项目有意将认证和授权拆开: - Keycloak 负责“你是谁” - 本地数据库负责“你能做什么” 来自 Keycloak 的 JWT 被视为外部身份。`PermissionClaimsTransformation` 会读取本地授权快照,并补充: - 本地 `sub`,对应本地 `UserId` - 角色声明 权限本身不会直接塞进令牌,而是通过本地权限服务完成判断: ```text RequirePermission -> PermissionAuthorizationRequirement -> PermissionAuthorizationHandler -> AuthorizationService -> Redis cache / PostgreSQL permissions ``` 这样既能保持端点用法简洁,也避免把大量权限信息塞进 Token。 ```csharp .RequireAuthorization(policy => policy.RequirePermission(Permissions.UsersRead)) ``` 当前权限使用刻意保持轻量,`GET /api/v1/users/me` 受 `users:read` 保护,其余大部分预订与评论操作目前仍是“已登录即可访问”。 ## 可观测性 项目基于 .NET 自带日志体系 + OpenTelemetry,而不是强依赖某个特定日志框架。 - `ServiceDefaults` 统一配置 OpenTelemetry 日志、链路、指标和 OTLP 导出 - `ActivityTrackingOptions` 将 `TraceId` / `SpanId` 加入日志上下文 - ASP.NET Core Instrumentation 自动补充 HTTP 语义字段 - `HttpLogging` 仅保留精简请求日志:方法、路径、状态码、耗时 - `RequestContextMiddleware` 负责补充业务上下文: - `X-Response-Time-Ms` - `Activity` 标签 `user.id` - logger scope `user.id` 默认不会记录查询字符串、请求体、响应体和请求头,避免噪音和敏感信息泄露。 ## 性能与接口管道 在性能相关能力上,项目优先使用 ASP.NET Core 内置机制: - Brotli / Gzip 响应压缩,优先优化 API 延迟 - Output Cache 使用具名短缓存策略 - `GET /api/v1/apartments/search` 使用 `.CacheOutput("Short")` - 注册和预订写接口使用定向限流 缓存没有全局开启,因为项目中存在大量已认证或强用户上下文接口。 ## 主要接口 所有业务接口统一挂在 `/api/v1` 下: - `POST /api/v1/users/register` - `GET /api/v1/users/me` - `GET /api/v1/apartments/search?startDate=2026-07-01&endDate=2026-07-05` - `POST /api/v1/bookings` - `GET /api/v1/bookings/{id}` - `POST /api/v1/bookings/{id}/confirm` - `POST /api/v1/bookings/{id}/reject` - `POST /api/v1/bookings/{id}/cancel` - `POST /api/v1/bookings/{id}/complete` - `POST /api/v1/bookings/{id}/reviews` ## 初始化数据 在 `Development` 环境下,应用会自动执行 EF Core 迁移,并初始化演示用户和示例公寓。 演示 Keycloak 用户会映射到本地业务用户,关联字段为 `users.identity_id`: - demo 用户名:`john.doe` - demo 密码:`Password1!` - demo 邮箱:`john.doe@bookify.local` - demo identity id:`11111111-1111-1111-1111-111111111111` - admin 用户名:`admin` - admin 密码:`Password1!` - admin 邮箱:`admin@bookify.local` - admin identity id:`99999999-9999-9999-9999-999999999999` 初始化公寓包括: - `Soho Loft` - `Lakeview Retreat` - `Old Town Studio` ## 启动方式 使用 Aspire AppHost 启动完整本地环境: ```powershell dotnet run --project src\AppHost\AppHost.csproj ``` 启动后可通过 Aspire Dashboard 访问: - WebApi - PostgreSQL - Redis - Seq - Keycloak Keycloak 默认地址: ```text http://localhost:18080 ``` 导入的 Realm / Client: - Realm:`bookify` - API Client:`bookify-api` - Service Account Client:`bookify-admin-client` 本地测试获取 Token: ```powershell Invoke-RestMethod ` -Method Post ` -Uri "http://localhost:18080/realms/bookify/protocol/openid-connect/token" ` -ContentType "application/x-www-form-urlencoded" ` -Body "grant_type=password&client_id=bookify-api&username=john.doe&password=Password1!" ``` 如果单独启动 WebApi,默认地址为: ```text http://localhost:5000 ``` Swagger 地址: ```text http://localhost:5000/swagger ``` ## 验证 ```powershell dotnet build Bookify.slnx dotnet test tests\Bookify.Domain.UnitTests\Bookify.Domain.UnitTests.csproj ``` ## 项目说明 - 这是一个偏学习与展示用途的整洁架构项目,不是现成的生产模板 - 授权部分刻意保持轻量,目前没有完整的权限管理后台 - 邮件服务目前只记录发送行为,没有对接真实 SMTP 或事务邮件服务 - 输出缓存采用按接口显式启用,避免缓存用户相关响应 - `Aspire.Hosting.Keycloak` 和 `Aspire.Keycloak.Authentication` 仍使用当前引用的预览版本线 - `OpenTelemetry.Instrumentation.EntityFrameworkCore` 仍使用当前引用的 beta 版本线 ## 致谢 项目在设计过程中参考了 Milan Jovanovic 关于整洁架构、DDD、CQRS、EF Core、Outbox、测试与授权分层的实践思路。 课程参考: [Pragmatic Clean Architecture](https://www.milanjovanovic.tech/pragmatic-clean-architecture)