Files
vibe_coding/.cursor/rules/references/019-modular-deep.md
2026-03-05 21:27:11 +08:00

480 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 019-modular.mdc (Deep Reference)
> 该文件为原始详细规范归档,供 Tier 3 按需读取。
---
# 🧩 Modular Architecture Standards
## 核心原则
| 原则 | 说明 | 违反案例 |
|------|------|---------|
| **单一职责** | 一个文件只做一件事 | 1000 行的 `index.vue` 包含业务+状态+样式 |
| **高内聚低耦合** | 同模块内紧密,跨模块通过接口 | 直接引用另一模块的内部 Service |
| **依赖方向单一** | 只允许 `UI → Service → Repository → Model` | Service 引用 Controller |
| **显式边界** | 模块通过公开的 index.ts 暴露 API | `import { InternalHelper } from '../order/utils/internal'` |
| **禁止循环依赖** | A 不能引用 B 的同时 B 引用 A | order 模块和 user 模块互相引用 |
---
## 前端模块划分
### 标准目录结构
```
src/
├── api/ # API 请求层(按模块分文件)
│ ├── user.ts # 用户相关接口
│ ├── order.ts # 订单相关接口
│ ├── permission.ts # 权限相关接口
│ └── index.ts # 统一导出
├── composables/ # 可复用逻辑(按功能命名 use*.ts
│ ├── useTable.ts # 表格通用逻辑
│ ├── useForm.ts # 表单通用逻辑
│ ├── useAuth.ts # 认证状态
│ ├── useDevice.ts # 设备检测
│ ├── usePermission.ts # 权限检查
│ └── useWebSocket.ts # WebSocket 管理
├── stores/ # Pinia 状态(按模块分文件)
│ ├── user.ts
│ ├── menu.ts
│ ├── setting.ts
│ └── index.ts # 统一导出
├── components/ # 全局公共组件
│ ├── ArtTable/ # 每个复杂组件独立目录
│ │ ├── index.vue # 主组件
│ │ ├── TableToolbar.vue # 子组件(内聚)
│ │ ├── TablePagination.vue
│ │ └── types.ts # 组件专属类型
│ ├── ArtForm/
│ └── ArtChart/
├── views/ # 页面(按业务域分目录)
│ ├── order/ # 订单业务域
│ │ ├── index.vue # 列表页
│ │ ├── detail.vue # 详情页
│ │ ├── components/ # 页面专属组件(不对外)
│ │ │ ├── OrderCard.vue
│ │ │ └── OrderTimeline.vue
│ │ ├── composables/ # 页面专属 composable
│ │ │ └── useOrderFilter.ts
│ │ └── types.ts # 页面专属类型
│ │
│ ├── user/
│ ├── permission/
│ └── dashboard/
├── utils/ # 纯工具函数(无副作用)
│ ├── format.ts # 格式化
│ ├── validate.ts # 校验
│ ├── crypto.ts # 加密工具
│ └── date.ts # 日期工具
├── directives/ # 自定义指令(每个指令独立文件)
│ ├── auth.ts # v-auth
│ ├── roles.ts # v-roles
│ └── index.ts # 统一注册
└── types/ # 全局类型定义
├── api.ts # API 响应/请求类型
├── models.ts # 业务实体类型
└── common.ts # 通用类型
```
### 文件拆分阈值
| 指标 | 警告 | 必须拆分 |
|------|------|---------|
| 文件行数 | > 200 行 | > 400 行 |
| 组件内 ref 数量 | > 8 个 | > 15 个 |
| 单个 composable 行数 | > 100 行 | > 200 行 |
| 单个 store 行数 | > 150 行 | > 300 行 |
### 组件拆分决策树
```
一个 .vue 文件是否应该拆分?
├─ 文件 > 200 行? → 考虑拆分
├─ 包含多个独立 UI 块? → 拆分为子组件
├─ 业务逻辑超过 50 行? → 提取为 composable
├─ 该组件在 > 2 个地方复用? → 移至 components/
└─ 否 → 保持不变
```
---
## 后端模块划分Hyperf DDD
### 标准目录结构
```
app/
├── Controller/ # HTTP 入口层(只做参数接收 + 响应格式化)
│ ├── OrderController.php
│ ├── UserController.php
│ └── Traits/
│ └── ApiResponse.php # 响应格式 Trait
├── Service/ # 业务逻辑层(核心业务规则)
│ ├── OrderService.php
│ ├── UserService.php
│ └── Dto/ # 数据传输对象
│ ├── OrderCreateDto.php
│ └── OrderListDto.php
├── Repository/ # 数据访问层(数据库/缓存操作)
│ ├── OrderRepository.php
│ ├── UserRepository.php
│ └── Contracts/ # Repository 接口
│ └── OrderRepositoryInterface.php
├── Model/ # Eloquent ORM 模型
│ ├── Order.php
│ ├── User.php
│ └── Traits/ # 可复用模型 Trait
│ ├── HasCreator.php
│ ├── HasSoftDeletes.php
│ └── HasDataPermission.php
├── Request/ # 表单验证(每个操作独立 Request
│ ├── Order/
│ │ ├── CreateOrderRequest.php
│ │ ├── UpdateOrderRequest.php
│ │ └── ListOrderRequest.php
│ └── User/
├── Event/ # 事件定义(命名:名词+动词过去式)
│ ├── OrderCreated.php
│ ├── OrderStatusChanged.php
│ └── UserLoggedIn.php
├── Listener/ # 事件监听器(一个事件一个 Listener
│ ├── SendOrderNotification.php
│ ├── LogOrderStatusChange.php
│ └── UpdateUserLastLogin.php
├── Middleware/ # 中间件(每个职责独立文件)
│ ├── AuthMiddleware.php
│ ├── PermissionMiddleware.php
│ ├── RateLimitMiddleware.php
│ └── AntiScrapingMiddleware.php
├── Exception/ # 异常定义(每类业务一个异常)
│ ├── BusinessException.php
│ ├── AuthException.php
│ ├── PermissionException.php
│ └── Handler/
│ └── AppExceptionHandler.php
├── Job/ # 异步任务AsyncQueue
│ ├── SendNotificationJob.php
│ └── GenerateReportJob.php
└── Command/ # 命令行工具
└── SyncPermissionCommand.php
```
### 各层职责边界
```php
// ✅ Controller — 只做参数绑定 + 调用 Service + 格式化响应
class OrderController
{
public function store(CreateOrderRequest $request): array
{
$dto = CreateOrderDto::fromRequest($request);
$order = $this->orderService->create($dto);
return $this->success(OrderResource::make($order));
}
}
// ✅ Service — 只做业务逻辑,调用 Repository 取数据
class OrderService
{
public function create(CreateOrderDto $dto): Order
{
// 业务规则:库存检查
$this->inventoryService->checkStock($dto->productId, $dto->quantity);
$order = $this->orderRepository->create($dto->toArray());
// 发事件(不直接发通知,解耦)
event(new OrderCreated($order));
return $order;
}
}
// ✅ Repository — 只做数据库操作,不包含业务规则
class OrderRepository
{
public function create(array $data): Order
{
return Order::create($data);
}
public function findWithItems(int $id): ?Order
{
return Order::with(['items', 'creator'])->find($id);
}
}
// ❌ 反模式Controller 直接操作 Model
class BadController
{
public function store(Request $request): array
{
$order = Order::create($request->all()); // ← 跳过 Service 层
Mail::to($order->user)->send(new OrderMail($order)); // ← Controller 不应发邮件
return ['data' => $order];
}
}
```
### 模块通信规范
| 通信方式 | 使用场景 | 实现 |
|---------|---------|------|
| **直接依赖注入** | 同层级调用Service → Service | `__construct` 注入 |
| **事件系统** | 跨模块解耦通知 | `event(new OrderCreated($order))` |
| **消息队列** | 耗时操作异步化 | `AsyncQueue::push(new SendEmailJob(...))` |
| **共享 Repository** | 跨模块读取数据 | 注入对方 Repository只读 |
| **禁止** | 跨模块调用内部方法 | ✘ `$userService->_internalCalc()` |
```php
// ✅ 事件解耦:订单模块不直接依赖通知模块
// app/Event/OrderCreated.php
class OrderCreated
{
public function __construct(public readonly Order $order) {}
}
// app/Listener/SendOrderNotification.php通知模块监听
#[Listener]
class SendOrderNotification implements ListenerInterface
{
public function listen(): array
{
return [OrderCreated::class];
}
public function process(object $event): void
{
$this->notificationService->notify($event->order->user_id, 'order_created', [
'order_id' => $event->order->id,
]);
}
}
```
---
## 禁止循环依赖
```
依赖方向规则(严格单向):
Controller → Service → Repository → Model
Event → Listener
Job异步
✅ Service 可以依赖 Repository
✅ Service 可以发布 Event
✅ Listener 可以依赖 Service
❌ Repository 不能依赖 Service
❌ Model 不能依赖 Service/Repository
❌ Event 不能依赖 ServiceEvent 是纯数据)
```
### 检测循环依赖
```bash
# PHP 项目
composer require maglnet/composer-require-checker --dev
# 前端项目ESLint 插件)
npm install eslint-plugin-import --save-dev
# .eslintrc 中配置 import/no-cycle
```
---
## Vue 组件模块化规范
### 组件分类
| 类型 | 位置 | 命名 | 特征 |
|------|------|------|------|
| **基础组件** | `src/components/` | `Art*` | 无业务逻辑,高复用 |
| **业务组件** | `src/views/{domain}/components/` | `{Domain}*` | 包含业务,不跨域复用 |
| **页面组件** | `src/views/{domain}/index.vue` | 路由直接映射 | 整合子组件和 store |
| **布局组件** | `src/layouts/` | `*Layout` | 全局页面框架 |
### 单文件组件结构SFC
```vue
<!-- 标准顺序script template style -->
<script setup>
// 1. 导入(分组排列)
import { computed, ref } from 'vue' // Vue core
import { useRouter } from 'vue-router' // Router
import { storeToRefs } from 'pinia' // Pinia
import { useOrderStore } from '@/stores/order' // Stores
import { useTable } from '@/composables/useTable' // Composables
import { OrderApi } from '@/api/order' // API
import OrderCard from './components/OrderCard.vue' // Local components
// 2. Props & EmitsJS 对象语法)
const props = defineProps({
domain: { type: String, required: true },
readonly: { type: Boolean, default: false },
})
const emit = defineEmits(['updated', 'deleted'])
// 3. Store
const orderStore = useOrderStore()
const { orders, loading } = storeToRefs(orderStore)
// 4. Composable复杂逻辑提取
const { tableData, pagination, fetchData } = useTable(OrderApi.list)
// 5. 本地状态(按功能分组,加注释分隔)
// --- Dialog state ---
const dialogVisible = ref(false)
const currentOrder = ref(null)
// --- Filter state ---
const searchForm = ref({ keyword: '', status: '' })
// 6. Computed
const filteredOrders = computed(() =>
orders.value.filter((o) => o.domain === props.domain)
)
// 7. Methods按功能分组
function openDialog(order) {
currentOrder.value = order
dialogVisible.value = true
}
async function handleDelete(id) {
await orderStore.delete(id)
emit('deleted', id)
}
// 8. Lifecycle
onMounted(() => fetchData())
</script>
<template>
<!-- 模板只做渲染不包含复杂逻辑 -->
</template>
<style scoped>
/* 组件作用域样式 */
</style>
```
---
## Composable 拆分规范
```typescript
// ✅ 单一职责useOrderFilter 只负责过滤逻辑
// src/views/order/composables/useOrderFilter.ts
export function useOrderFilter(orders) {
const status = ref('')
const keyword = ref('')
const dateRange = ref(null)
const filtered = computed(() => {
return orders.value.filter((order) => {
if (status.value && order.status !== status.value) return false
if (keyword.value && !order.orderNo.includes(keyword.value)) return false
if (dateRange.value) {
const [start, end] = dateRange.value
if (order.createdAt < start || order.createdAt > end) return false
}
return true
})
})
function reset() {
status.value = ''
keyword.value = ''
dateRange.value = null
}
return { status, keyword, dateRange, filtered, reset }
}
// ❌ 反模式:把表格、过滤、编辑逻辑都放在一个 composable
export function useOrderPage() { // 太大了
// ... 300 行混合逻辑
}
```
---
## 前端导入层级边界
```
导入方向规则(严格单向):
views/ → composables/ → stores/ → api/ → utils/
│ │
│ └→ components/(仅消费,不反向引用 views/
└→ components/
✅ views/ 可以引用 composables/、stores/、api/、components/
✅ composables/ 可以引用 stores/、api/、utils/
✅ stores/ 可以引用 api/、utils/
✅ components/ 可以引用 composables/、utils/(通用组件不引用 stores/
❌ components/core/ 不得引用 stores/(通用组件不依赖业务状态)
❌ api/ 不得引用 stores/ 或 views/
❌ utils/ 不得引用任何其他层(纯函数,无副作用)
❌ 跨业务域直接引用内部组件(通过公开 index.ts 导出)
```
**ESLint 配置建议**
```typescript
// .eslintrc — import/no-restricted-paths
{
rules: {
'import/no-restricted-paths': ['error', {
zones: [
{ target: './src/utils', from: './src/stores' },
{ target: './src/utils', from: './src/api' },
{ target: './src/api', from: './src/stores' },
{ target: './src/components/core', from: './src/stores' },
],
}],
},
}
```
---
## 规则汇总
- 单个 `.vue` 文件超过 200 行必须拆分,超过 400 行禁止提交
- 业务逻辑超过 50 行必须提取为 composable
- Composable 必须以 `use` 开头,返回对象必须解构友好
- 每个 API 模块对应一个文件(`src/api/{module}.ts`
- 禁止在 Controller 中操作 Model必须经过 Service + Repository
- 禁止在 Model 中调用 Service保持 Model 纯净)
- 禁止跨业务域直接引用内部组件(通过公开 `index.ts` 导出)
- 事件名必须是:`{域}` + `{动作的过去时}` (如 `OrderCreated`)
- 每次 PR 提交前运行 `eslint --rule import/no-cycle` 检查循环依赖
- 前端导入方向必须单向:`views → composables → stores → api → utils`
- 通用组件 (`components/core/`) 禁止依赖业务状态 (`stores/`)