Files
rui-docs/superpowers/specs/2026-06-06-api-collaboration-design.md
T

484 lines
13 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.
# 前后端智能协作方案设计文档
> **文档版本**: v1.0
> **创建日期**: 2026-06-06
> **适用范围**: rui-frontend 所有前端项目(admin-ui、cashier-mobile、cashier-customer
---
## 一、问题背景
### 1.1 当前痛点
admin-ui 项目中存在**表单字段与后台 API 不同步**的问题:
1. **表单字段硬编码** - 19 个 FormDialog 组件全部采用手动定义字段(如 `UserFormDialog.vue` 中的 `username`, `password`, `userType` 等)
2. **缺少 TypeScript 类型** - Service 层大量使用 `any` 类型,没有与后端 DTO 对应的接口定义
3. **BaseService 泛型未充分利用** - `BaseService<T>` 定义了泛型,但子类没有传入具体类型
4. **API 文档未利用** - 后端已提供 `/v3/api-docs`SpringDoc OpenAPI),但前端未使用
### 1.2 影响
- 后端字段变更时,前端需要手动修改所有相关表单
- 缺少编译时类型检查,运行时容易出错
- 前后端协作效率低,沟通成本高
---
## 二、设计目标
1. **类型安全** - 前端类型与后端 API 自动同步
2. **减少样板代码** - 表单配置化,减少 70% 重复代码
3. **支持自定义扩展** - 复杂表单可自定义字段和逻辑
4. **多模块支持** - 适配微服务架构(聚合启动器 + 独立服务)
5. **自动化检测** - 防止前后端不同步上线
---
## 三、架构设计
### 3.1 整体架构
```
API设计规范.md (单一事实来源)
├── 包含所有 api-docs 地址
└── 由后端团队维护
↓ 自动解析
scripts/parse-api-config.ts
└── 读取并解析 Markdown 中的 URL
└── 识别聚合启动器和独立服务
↓ 动态配置
scripts/generate-api.ts
├── 聚合启动器模块(通过 group 参数获取)
├── 独立服务模块(直接访问)
├── 生成类型定义文件
├── 生成统一导出
└── 生成模块映射
↓ 类型文件
src/types/
├── system-api.d.ts # 系统服务类型
├── user-api.d.ts # 用户服务类型
├── cashier-api.d.ts # 收银服务类型
├── api.d.ts # 统一导出
└── api-modules.json # 模块映射
↓ 类型驱动
前端代码
├── Service 层(类型安全)
├── useApiForm() 组合式函数
├── ApiFormDialog 组件
└── 自定义扩展(插槽机制)
```
### 3.2 核心组件
| 组件 | 职责 | 说明 |
|------|------|------|
| `parse-api-config.ts` | 解析 API 规范文档 | 从 Markdown 读取 api-docs 地址 |
| `generate-api.ts` | 生成类型定义 | 调用 openapi-typescript 生成 .d.ts |
| `useApiForm()` | 类型驱动表单管理 | 组合式函数,自动生成表单配置 |
| `ApiFormDialog` | 配置化表单组件 | 根据字段配置自动渲染表单 |
| `BaseService<T>` | 类型安全 Service 基类 | 保留现有架构,增强类型 |
---
## 四、详细设计
### 4.1 API 配置解析(parse-api-config.ts
**核心功能**:从 `API设计规范.md` 自动读取 api-docs 地址
**聚合启动器识别规则**
- 端口 9399 → 聚合启动器,包含多个模块(/user, /system
- 通过 `?group=xxx` 参数获取子模块
- 其他端口 → 独立服务
**配置结构**
```typescript
interface ApiModuleConfig {
name: string // 模块名:user, system, cashier
url: string // api-docs URL
output: string // 输出路径
prefix: string // API 路径前缀
description: string // 模块描述
aggregator?: string // 所属聚合器(如果有)
}
```
**解析示例**
```markdown
# API设计规范.md
http://localhost:9399/v3/api-docs # 聚合启动器 API 文档(开发调试)
http://localhost:9601/v3/api-docs # 收银服务 API 文档
```
解析结果:
- `9399` → 聚合启动器 → 展开为 user、system 两个模块
- `9601` → 独立服务 → cashier 模块
### 4.2 类型生成(generate-api.ts
**生成流程**
1. 读取 API设计规范.md
2. 识别聚合启动器和独立服务
3. 为每个模块生成类型文件
4. 生成统一导出文件
5. 生成模块映射文件
**输出文件结构**
```
src/types/
├── user-api.d.ts # 用户服务类型(来自聚合启动器)
├── system-api.d.ts # 系统服务类型(来自聚合启动器)
├── cashier-api.d.ts # 收银服务类型(独立服务)
├── api.d.ts # 统一导出
│ └── export type * from './user-api'
│ └── export type * from './system-api'
│ └── export type * from './cashier-api'
└── api-modules.json # 模块映射(用于运行时)
└── {
└── "aggregator": [
└── { "name": "user", "prefix": "/user" },
└── { "name": "system", "prefix": "/system" }
└── ]
└── }
```
### 4.3 类型驱动表单(useApiForm
**核心功能**:根据 TypeScript 类型自动生成表单配置
**接口设计**
```typescript
interface FieldConfig<T = any> {
key: keyof T // 字段名(类型安全)
label: string // 字段标签
type: FieldType // 字段类型
required?: boolean // 是否必填
rules?: any[] // 验证规则
options?: Option[] // 选项(select/radio/checkbox
disabled?: boolean | ((form: T) => boolean)
placeholder?: string
props?: Record<string, any>
}
interface FormConfig<T> {
initial?: Partial<T> // 初始值
fields: FieldConfig<T>[] // 字段配置
onSubmit: (data: T) => Promise<void>
beforeSubmit?: (data: T) => boolean | string
}
function useApiForm<T extends Record<string, any>>(
config: FormConfig<T>
): {
formRef: Ref<FormInstance>
form: Ref<Partial<T>>
rules: ComputedRef<Record<string, any[]>>
loading: Ref<boolean>
handleSubmit: () => Promise<boolean>
resetForm: (initial?: Partial<T>) => void
}
```
### 4.4 配置化表单组件(ApiFormDialog
**核心功能**:根据字段配置自动渲染表单元素
**支持的字段类型**
| 类型 | 组件 | 说明 |
|------|------|------|
| `input` | ElInput | 文本输入 |
| `textarea` | ElInput(type="textarea") | 多行文本 |
| `select` | ElSelect | 下拉选择 |
| `radio` | ElRadioGroup | 单选按钮 |
| `checkbox` | ElCheckboxGroup | 多选框 |
| `number` | ElInputNumber | 数字输入 |
| `tree-select` | ElTreeSelect | 树形选择 |
| `date` | ElDatePicker | 日期选择 |
| `datetime` | ElDatePicker | 日期时间选择 |
**自定义扩展机制**
```vue
<ApiFormDialog
v-model:visible="visible"
v-model:form="form"
:fields="fields"
:rules="rules"
@submit="handleSubmit"
>
<!-- 自定义字段插槽 -->
<template #custom-fields="{ form }">
<el-form-item label="自定义部门">
<el-tree-select v-model="customDeptIds" :data="deptTree" />
</el-form-item>
</template>
</ApiFormDialog>
```
### 4.5 Service 层增强
**保留现有 BaseService 架构**,增强类型安全:
```typescript
// 现有架构保持不变
class BaseService<T = any> {
protected baseUrl: string
async page(params: PageParams & Record<string, any>): Promise<PageResult<T>>
async list(params?: Record<string, any>): Promise<T[]>
async getById(id: number | string): Promise<T>
async add(data: Partial<T>): Promise<T>
async update(data: Partial<T> & { id: number | string }): Promise<boolean>
async remove(id: number | string): Promise<boolean>
}
// 使用时传入具体类型
class UserService extends BaseService<UserDTO> {
constructor() {
super('/user/admin/user')
}
}
```
---
## 五、使用示例
### 5.1 标准表单(完全配置化)
```vue
<script setup lang="ts">
import { useApiForm } from '@/composables/useApiForm'
import { userService } from '@/service/user/userService'
import type { components } from '@/types/user-api'
type UserDTO = components['schemas']['UserDTO']
const { formRef, form, rules, loading, handleSubmit, resetForm } = useApiForm<UserDTO>({
initial: { userType: 1, status: 1 },
fields: [
{ key: 'username', label: '用户名', type: 'input', required: true },
{ key: 'password', label: '密码', type: 'input', required: true, props: { type: 'password' } },
{ key: 'userType', label: '用户类型', type: 'select', options: [
{ label: '普通用户', value: 1 },
{ label: '管理员', value: 2 }
]},
{ key: 'status', label: '状态', type: 'radio', options: [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 }
]}
],
onSubmit: async (data) => {
await userService.add(data)
}
})
</script>
<template>
<ApiFormDialog
v-model:visible="visible"
v-model:form="form"
title="新增用户"
:fields="fields"
:rules="rules"
:loading="loading"
@submit="handleSubmit"
/>
</template>
```
### 5.2 自定义扩展表单
```vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useApiForm } from '@/composables/useApiForm'
import { roleService } from '@/service/system/roleService'
import type { components } from '@/types/system-api'
type RoleDTO = components['schemas']['RoleDTO']
const selectedDeptIds = ref<number[]>([])
const showCustomDept = computed(() => form.value.dataScope === 5)
const { form, fields, rules, handleSubmit } = useApiForm<RoleDTO>({
fields: [
{ key: 'roleCode', label: '角色编码', type: 'input', required: true },
{ key: 'roleName', label: '角色名称', type: 'input', required: true },
{ key: 'dataScope', label: '数据范围', type: 'select', options: [
{ label: '全部', value: 1 },
{ label: '自定义', value: 5 }
]}
],
onSubmit: async (data) => {
// 自定义验证
if (data.dataScope === 5 && selectedDeptIds.value.length === 0) {
throw new Error('请选择部门')
}
await roleService.add(data)
}
})
</script>
<template>
<ApiFormDialog
v-model:visible="visible"
v-model:form="form"
:fields="fields"
:rules="rules"
@submit="handleSubmit"
>
<template #custom-fields>
<el-form-item v-if="showCustomDept" label="选择部门" required>
<el-tree-select v-model="selectedDeptIds" :data="deptTree" />
</el-form-item>
</template>
</ApiFormDialog>
</template>
```
---
## 六、自动化机制
### 6.1 Git Hook(提交前检查)
```typescript
// scripts/check-api-changes.ts
function checkApiChanges() {
const currentHash = execSync('git hash-object src/types/*.d.ts').toString()
try {
execSync('pnpm api:generate', { stdio: 'pipe' })
const newHash = execSync('git hash-object src/types/*.d.ts').toString()
if (currentHash !== newHash) {
console.error('❌ API types have changed!')
console.error('Please run "pnpm api:generate" and commit the changes.')
process.exit(1)
}
} catch (error) {
console.warn('⚠️ Could not check API changes')
}
}
```
### 6.2 脚本命令
```json
{
"scripts": {
"api:generate": "tsx scripts/generate-api.ts",
"api:check": "tsx scripts/check-api-changes.ts",
"api:watch": "nodemon --watch ../docs/standards/API设计规范.md --exec 'pnpm api:generate'"
}
}
```
---
## 七、扩展性设计
### 7.1 新增模块
当后端新增模块时,只需在 `API设计规范.md` 中添加:
```markdown
http://localhost:9399/v3/api-docs?group=订单服务 # 订单服务 API 文档
http://localhost:9701/v3/api-docs # 新独立服务 API 文档
```
前端自动识别:
- 端口 9399 → 聚合启动器,通过 `group` 参数获取
- 新端口 9701 → 独立服务,直接访问
### 7.2 新增字段类型
`ApiFormDialog.vue` 中添加新的字段类型渲染逻辑即可。
---
## 八、实施计划
### Phase 1: 基础搭建(1-2 天)
1. 安装依赖:`openapi-typescript`, `tsx`, `nodemon`
2. 创建 `scripts/parse-api-config.ts` - 解析 API 规范
3. 创建 `scripts/generate-api.ts` - 生成类型
4. 配置 package.json 脚本
### Phase 2: 类型生成(1 天)
1. 运行 `pnpm api:generate` 生成初始类型
2. 验证类型正确性
3. 提交生成的类型文件
### Phase 3: 表单工具(2-3 天)
1. 创建 `useApiForm()` 组合式函数
2. 创建 `ApiFormDialog.vue` 组件
3. 编写单元测试
### Phase 4: 试点迁移(2-3 天)
1. 选择 2-3 个简单表单进行迁移
2. 验证方案可行性
3. 收集反馈优化
### Phase 5: 全面推广(1-2 周)
1. 迁移所有标准表单
2. 保留自定义表单的特殊逻辑
3. 编写迁移文档
### Phase 6: 自动化(1 天)
1. 配置 Git Hook
2. 配置 CI/CD 检查
3. 编写使用文档
---
## 九、风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| 后端 API 文档不准确 | 生成的类型错误 | 建立 API 文档审核机制 |
| 聚合启动器不可用 | 无法生成类型 | 支持独立服务直接访问 |
| 复杂表单迁移困难 | 影响进度 | 保留自定义扩展机制 |
| 团队成员学习成本 | 初期效率下降 | 提供详细文档和示例 |
---
## 十、成功标准
1. ✅ 所有 Service 层使用具体类型替代 `any`
2. ✅ 标准表单代码量减少 70%
3. ✅ 后端字段变更时,前端编译期即可发现
4. ✅ 新增模块时,类型自动生成
5. ✅ 提交时自动检测 API 变更
---
> **下一步**: 编写实现计划(implementation plan