# 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 不能依赖 Service(Event 是纯数据) ``` ### 检测循环依赖 ```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 ``` --- ## 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/`)