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

13 KiB
Raw Permalink Blame History

前后端智能协作方案设计文档

文档版本: 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-docsSpringDoc 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 参数获取子模块
  • 其他端口 → 独立服务

配置结构

interface ApiModuleConfig {
  name: string        // 模块名:user, system, cashier
  url: string         // api-docs URL
  output: string      // 输出路径
  prefix: string      // API 路径前缀
  description: string // 模块描述
  aggregator?: string // 所属聚合器(如果有)
}

解析示例

# 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 类型自动生成表单配置

接口设计

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 日期时间选择

自定义扩展机制

<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 架构,增强类型安全:

// 现有架构保持不变
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 标准表单(完全配置化)

<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 自定义扩展表单

<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(提交前检查)

// 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 脚本命令

{
  "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 中添加:

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