# 项目架构

## 决策

项目采用 Laravel 9 的分层单体：单仓库、单应用、单数据库、统一部署。保留 Controller、FormRequest、Resource、Model、Service 等框架约定，并增加实际被业务使用的 DAO 层；不引入模块框架和空壳式 Repository。

`Backend` 和 `Frontend` 是两个 HTTP/用例入口边界；每个入口内部再按业务功能分目录。Model/DAO 按领域目录归类。这不是两个可独立部署的模块，它们共享数据库、缓存和基础设施。

## 分层

```text
HTTP Route
  → Middleware
  → Controller + FormRequest
  → Service（规则和事务编排）
  → DAO（查询和持久化）
  → Eloquent Model / Database
  → JsonResource
  → JSON Response
```

- `routes/frontend.php`：读者、作者和互动接口。
- `routes/backend.php`：运营后台接口。
- `Controllers`：接收请求、鉴权和用例编排，不写复杂查询。
- `Requests`：输入校验和输入授权。
- `Services`：事务、跨模型规则、复杂读写流程。
- `Dao`：集中封装 Eloquent 查询、关系同步和明细表读写；不能返回 HTTP Response。
- `Models`：关系、cast、作用域及贴近模型的简单行为。
- `Resources`：对外 JSON 契约；不直接返回 Eloquent Model。
- `Exceptions/Handler`：统一错误映射和生产环境错误脱敏。
- `Support`：无业务归属的响应基础能力。

目录中的入口与功能保持对称：

```text
Controllers/Backend/Article
Requests/Backend/Article
Resources/Backend/Article
Services/Backend/Article
Dao/Content
Models/Content

Controllers/Frontend/Article
Resources/Frontend/Article
Services/Frontend/Article
```

功能没有对应职责时不创建空目录。每个业务层最多两级目录，例如 `Services/Backend/Article`；Model 与 DAO 不使用 Backend/Frontend 切分，因为同一实体和持久化能力可被两个入口复用。

## Service 职责边界

Service 按“业务能力”拆分，不使用 `CommonService`、`ContentService`、`InteractionService` 这类不断膨胀的集合类，也不机械地为每个方法创建一个类。

```text
Frontend/Article/ArticleQueryService       # 文章详情、相关文章和文章读取模型
Frontend/Feed/FeedService                  # 公共内容流和关注内容流
Frontend/Category/CategoryQueryService     # 前台分类发现
Frontend/Tag/TagQueryService               # 热门标签发现
Frontend/User/AuthorQueryService           # 作者详情和作者推荐
Frontend/Interaction/LikeService           # 点赞与取消点赞
Frontend/Interaction/BookmarkService       # 收藏、取消收藏和收藏列表
Frontend/Interaction/CommentService        # 评论列表、创建与删除
Frontend/Interaction/FollowService         # 关注与取消关注
Frontend/Library/ReadingHistoryService     # 阅读进度与历史
Backend/Article/ArticleService              # 后台内容查询、编辑和治理
Backend/Article/ArticleStatusService        # 发布权限和状态流转
Backend/Article/ArticleVersionService       # 版本列表与恢复
Backend/Review/ArticleReviewService         # 投稿与审核状态机
Backend/Column/ColumnService                # 专栏及文章排序
```

判断是否需要继续拆分的标准是：变化原因是否不同、事务边界是否不同、依赖是否不同。属于同一能力的正反操作保留在一起，例如收藏/取消收藏；文章 CRUD 与文章发布状态流转则分开。

## 依赖规则

1. Frontend 与 Backend 不能互相引用 Controller、Request 或 Resource；共享逻辑下沉到 Model、Support 或明确的共享服务。
2. Service 不依赖 HTTP Request、Controller 或 Resource，也不直接返回 HTTP Response。
3. Controller 不持有事务和 SQL；Service 不直接拼业务查询，持久化下沉 DAO。
4. Resource 负责字段、嵌套资源和分页契约，不承载写操作。
5. Resource 不执行数据库查询；Controller/Service/DAO 必须提前 eager load 所需关系和计数。
6. 数据一致性同时依赖数据库约束和事务，不能只靠应用层判断。
7. 当前 URL 不使用 V1；只有出现必须长期并存的破坏性协议变更时才引入版本。

## 认证与权限

- `api` guard 使用 `tymon/jwt-auth`；access token 可刷新，注销和刷新会使旧 token 进入黑名单。
- JWT 只负责身份认证；项目自有 `roles`、`permissions`、`role_user`、`permission_role` 表负责授权。
- 后台路由使用 `permission:<slug>` 原子权限，不把角色名称硬编码在控制器。
- 默认角色为 reader、operator、reviewer、admin；角色权限映射由 Seeder 初始化，后续可直接做管理界面。
- 依赖具体文章所有权的规则增加后，使用 Policy 与原子权限共同判断。
- JWT 黑名单依赖缓存；多实例生产部署必须使用共享 Redis，不能使用本机文件缓存。

## 响应与异常

- 成功实体由 Laravel `JsonResource` 输出。
- 分页使用标准 `data + links + meta`，不再维护自定义分页转换器。
- `ApiResourceResponse` 只添加 `code/message/trace_id` 和 HTTP 状态，不解析资源内容。
- 预期业务失败抛 `ApiException`；认证、授权、校验和模型不存在使用框架异常。
- 未知异常只在 debug 环境返回详情，生产环境返回通用信息，并写入 exceptions 日志。

## 日志与可观测性

日志只有一份 Laravel 配置，但包含多个命名 channel。每个入口功能组通过 `module.log:<channel>` 写入自己的 daily channel，记录 trace ID、路由、耗时、状态、用户和 IP；异常另写 `exceptions` channel。中间件会验证 channel 是否真实存在，测试也会检查所有路由引用，避免配置拼写错误在运行时才暴露。

## 数据与性能

- 点赞、收藏、关注、阅读历史使用联合唯一索引保证幂等。
- 文章元数据、当前正文、历史版本、审核记录、专栏、媒体和日指标分表存储。
- 高频列表使用计数字段，并在事务内维护。
- 阅读量按用户或 IP 在缓存窗口内去重。
- 列表限制最大分页大小，关系使用 eager loading 和批量计数。
- 生产建议 MySQL/PostgreSQL + Redis；搜索规模上升后再接 Meilisearch/Elasticsearch。

## 演进边界

优先在单体内使用 Event、Listener、Job 和 Policy 扩展通知、审核、搜索索引与统计。只有独立伸缩、独立发布或组织边界明确时，再拆搜索、推荐或媒体服务。
