cursor开发-电商平台开发供应商管理模块实战详解
🎯 深度剖析:智能CRUD生成器的实战详解
📋 场景设定
我们接到一个真实需求:为一个电商平台开发供应商管理模块
业务需求
- 供应商基本信息管理
- 资质文件上传管理
- 合作状态跟踪
- 绩效评估记录
🔍 第一步:原始需求转化
传统开发方式
- 分析需求 → 2. 设计数据库 → 3. 写Model → 4. 写Service → 5. 写Controller → 6. 写前端 → 7. 写测试 → 8. 部署...
预计时间: 2-3天
使用Cursor智能CRUD模板
预计时间: 30分钟
🚀 第二步:模板实战演示
模板原始内容
基于以下实体生成完整的CRUD系统:
实体:[实体名称]
属性:
- id: number (主键)
- name: string (必填)
- [其他属性]
需要生成:
1. 数据库迁移脚本
2. 实体类/模型
3. Service层(业务逻辑)
4. Controller层(API接口)
5. DTO/验证器
6. 前端组件(列表/表单/详情)
7. 对应的API测试
技术栈:[后端框架] + [前端框架]
🎯 第三步:具体化模板参数
修改后的精准Prompt
基于以下实体生成完整的CRUD系统:
实体:Supplier(供应商)
属性:
- id: string (UUID主键)
- name: string (供应商名称,必填,唯一)
- code: string (供应商编码,唯一)
- type: enum('manufacturer', 'distributor', 'wholesaler')
- contactPerson: string (联系人)
- phone: string (联系电话,验证格式)
- email: string (邮箱,验证格式)
- address: object (地址对象,包含省市区详情)
- status: enum('active', 'inactive', 'pending', 'blacklisted')
- creditRating: number (信用评级 1-5)
- registeredAt: Date (注册时间)
- updatedAt: Date (更新时间)
- documents: array (资质文件列表,支持多文件)
关联实体:
- 供应商资质文件 (SupplierDocument)
- 供应商联系人 (SupplierContact)
- 采购订单 (PurchaseOrder)
需要生成:
1. 数据库迁移脚本 (MySQL 8.0)
2. Sequelize模型 (Node.js + TypeScript)
3. Service层 (包含业务逻辑和验证)
4. Controller层 (RESTful API,包含分页、过滤、排序)
5. DTO/验证器 (使用class-validator)
6. 前端组件 (React + TypeScript + Ant Design)
- 供应商列表页 (带搜索、筛选、批量操作)
- 供应商详情页
- 供应商表单 (创建/编辑)
- 资质文件上传组件
7. 对应的API测试 (Jest + Supertest)
8. Swagger文档注解
技术栈:
- 后端:Node.js + Express + Sequelize + TypeScript
- 前端:React 18 + TypeScript + Ant Design 5
- 数据库:MySQL 8.0
要求:
- 遵循RESTful最佳实践
- 完整的错误处理
- 输入验证和清理
- 日志记录
- 分页性能优化
- 文件上传到AWS S3
- 操作审计日志
🧠 第四步:Cursor的处理过程解析
阶段1:数据库设计(2分钟)
Cursor会生成:
1.1 迁移脚本
-- suppliers表
CREATE TABLE suppliers (
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
name VARCHAR(255) NOT NULL UNIQUE,
code VARCHAR(50) NOT NULL UNIQUE,
type ENUM('manufacturer', 'distributor', 'wholesaler') NOT NULL,
contact_person VARCHAR(100),
phone VARCHAR(20),
email VARCHAR(255),
address JSON,
status ENUM('active', 'inactive', 'pending', 'blacklisted') DEFAULT 'pending',
credit_rating TINYINT CHECK (credit_rating BETWEEN 1 AND 5),
registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL,
INDEX idx_status (status),
INDEX idx_type (type),
INDEX idx_credit_rating (credit_rating)
);
-- supplier_documents表(关联)
CREATE TABLE supplier_documents (
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
supplier_id VARCHAR(36) NOT NULL,
document_type ENUM('business_license', 'tax_certificate', 'quality_certificate') NOT NULL,
file_url VARCHAR(500) NOT NULL,
verified BOOLEAN DEFAULT FALSE,
verified_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE CASCADE,
INDEX idx_supplier_id (supplier_id),
INDEX idx_document_type (document_type)
);
1.2 Sequelize模型
// src/models/Supplier.ts
import { Model, DataTypes, Optional } from 'sequelize';
import sequelize from '../config/database';
interface SupplierAttributes {
id: string;
name: string;
code: string;
type: 'manufacturer' | 'distributor' | 'wholesaler';
contactPerson: string;
phone: string;
email: string;
address: {
province: string;
city: string;
district: string;
detail: string;
postalCode?: string;
};
status: 'active' | 'inactive' | 'pending' | 'blacklisted';
creditRating: number;
registeredAt: Date;
updatedAt: Date;
}
interface SupplierCreationAttributes
extends Optional<SupplierAttributes, 'id' | 'status' | 'creditRating' | 'registeredAt'> {}
class Supplier extends Model<SupplierAttributes, SupplierCreationAttributes>
implements SupplierAttributes {
public id!: string;
public name!: string;
// ... 所有属性定义
// 关联定义
public readonly documents?: SupplierDocument[];
static associate(models: any) {
Supplier.hasMany(models.SupplierDocument, {
foreignKey: 'supplierId',
as: 'documents'
});
}
}
Supplier.init({
// 详细的字段定义
}, {
sequelize,
modelName: 'Supplier',
tableName: 'suppliers',
timestamps: true,
paranoid: true, // 软删除
});
阶段2:业务逻辑层(3分钟)
2.1 DTO和验证器
// src/dto/create-supplier.dto.ts
import {
IsString, IsEmail, IsPhoneNumber, IsEnum,
IsNumber, Min, Max, IsObject, ValidateNested,
IsOptional, IsArray, Length
} from 'class-validator';
import { Type } from 'class-transformer';
class AddressDto {
@IsString()
province: string;
@IsString()
city: string;
@IsString()
district: string;
@IsString()
detail: string;
@IsOptional()
@IsString()
@Length(6, 6)
postalCode?: string;
}
export class CreateSupplierDto {
@IsString()
@Length(2, 255)
name: string;
@IsString()
@Length(3, 50)
code: string;
@IsEnum(['manufacturer', 'distributor', 'wholesaler'])
type: string;
@IsOptional()
@IsString()
@Length(2, 100)
contactPerson?: string;
@IsOptional()
@IsPhoneNumber('CN')
phone?: string;
@IsOptional()
@IsEmail()
email?: string;
@IsOptional()
@IsObject()
@ValidateNested()
@Type(() => AddressDto)
address?: AddressDto;
@IsOptional()
@IsNumber()
@Min(1)
@Max(5)
creditRating?: number;
@IsOptional()
@IsArray()
documentIds?: string[];
}
2.2 Service层核心逻辑
// src/services/supplier.service.ts
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { Supplier } from '../models/Supplier';
import { CreateSupplierDto, UpdateSupplierDto } from '../dto';
import { Op } from 'sequelize';
import { DocumentService } from './document.service';
@Injectable()
export class SupplierService {
constructor(
@InjectModel(Supplier)
private supplierModel: typeof Supplier,
private documentService: DocumentService
) {}
async create(createDto: CreateSupplierDto, userId: string): Promise<Supplier> {
// 检查唯一性
const existing = await this.supplierModel.findOne({
where: {
[Op.or]: [
{ name: createDto.name },
{ code: createDto.code }
]
}
});
if (existing) {
throw new ConflictException(
existing.name === createDto.name
? '供应商名称已存在'
: '供应商编码已存在'
);
}
// 创建供应商
const supplier = await this.supplierModel.create({
...createDto,
createdBy: userId,
status: 'pending'
});
// 关联文档
if (createDto.documentIds?.length) {
await this.documentService.linkToSupplier(
createDto.documentIds,
supplier.id
);
}
return this.findById(supplier.id);
}
async findAll(params: {
page?: number;
limit?: number;
search?: string;
status?: string;
type?: string;
minRating?: number;
sortBy?: string;
sortOrder?: 'ASC' | 'DESC';
}): Promise<{ data: Supplier[]; total: number; page: number }> {
const {
page = 1,
limit = 20,
search,
status,
type,
minRating,
sortBy = 'createdAt',
sortOrder = 'DESC'
} = params;
const offset = (page - 1) * limit;
const where: any = {};
// 构建查询条件
if (search) {
where[Op.or] = [
{ name: { [Op.like]: `%${search}%` } },
{ code: { [Op.like]: `%${search}%` } },
{ contactPerson: { [Op.like]: `%${search}%` } }
];
}
if (status) where.status = status;
if (type) where.type = type;
if (minRating) where.creditRating = { [Op.gte]: minRating };
// 性能优化:分开查询计数和数据
const { count, rows } = await this.supplierModel.findAndCountAll({
where,
limit,
offset,
order: [[sortBy, sortOrder]],
include: [
{
association: 'documents',
required: false,
where: { verified: true },
limit: 3 // 只加载前3个已验证文档
}
],
distinct: true // 正确计数
});
return {
data: rows,
total: count,
page: Math.ceil(count / limit)
};
}
// ... 其他方法:findById, update, delete, changeStatus, etc.
}
阶段3:API层(2分钟)
3.1 RESTful Controller
// src/controllers/supplier.controller.ts
import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
Query,
UseGuards,
UseInterceptors,
UploadedFiles,
ParseIntPipe,
DefaultValuePipe
} from '@nestjs/common';
import { FileFieldsInterceptor } from '@nestjs/platform-express';
import { ApiTags, ApiOperation, ApiResponse, ApiConsumes } from '@nestjs/swagger';
import { SupplierService } from '../services/supplier.service';
import { CreateSupplierDto, UpdateSupplierDto } from '../dto';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { RolesGuard } from '../guards/roles.guard';
import { Roles } from '../decorators/roles.decorator';
import { SupplierResponseDto } from '../dto/supplier-response.dto';
@ApiTags('suppliers')
@Controller('api/v1/suppliers')
@UseGuards(JwtAuthGuard, RolesGuard)
export class SupplierController {
constructor(private readonly supplierService: SupplierService) {}
@Post()
@Roles('admin', 'purchaser')
@ApiOperation({ summary: '创建新供应商' })
@ApiResponse({ status: 201, type: SupplierResponseDto })
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileFieldsInterceptor([
{ name: 'businessLicense', maxCount: 1 },
{ name: 'otherDocuments', maxCount: 10 }
]))
async create(
@Body() createDto: CreateSupplierDto,
@UploadedFiles() files: any,
@Request() req
) {
// 处理文件上传
const documentIds = await this.uploadDocuments(files);
createDto.documentIds = documentIds;
return this.supplierService.create(createDto, req.user.id);
}
@Get()
@ApiOperation({ summary: '获取供应商列表(分页)' })
@ApiResponse({ status: 200, type: [SupplierResponseDto] })
async findAll(
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
@Query('limit', new DefaultValuePipe(20), ParseIntPipe) limit: number,
@Query('search') search?: string,
@Query('status') status?: string,
@Query('type') type?: string,
@Query('minRating', new DefaultValuePipe(1), ParseIntPipe) minRating?: number,
@Query('sortBy') sortBy?: string,
@Query('sortOrder') sortOrder?: 'ASC' | 'DESC'
) {
return this.supplierService.findAll({
page,
limit,
search,
status,
type,
minRating,
sortBy,
sortOrder
});
}
@Get(':id')
@ApiOperation({ summary: '获取供应商详情' })
@ApiResponse({ status: 200, type: SupplierResponseDto })
async findOne(@Param('id') id: string) {
const supplier = await this.supplierService.findById(id);
if (!supplier) {
throw new NotFoundException(`供应商 ${id} 不存在`);
}
return supplier;
}
// ... PUT, DELETE, PATCH endpoints
}
阶段4:前端组件(5分钟)
4.1 React供应商列表页
// src/pages/supplier/SupplierList.tsx
import React, { useState, useEffect } from 'react';
import {
Table,
Button,
Space,
Input,
Select,
Card,
Tag,
Modal,
Form,
message,
Row,
Col,
Statistic
} from 'antd';
import {
SearchOutlined,
PlusOutlined,
EditOutlined,
EyeOutlined,
DeleteOutlined,
FilterOutlined
} from '@ant-design/icons';
import { useRequest, useToggle } from 'ahooks';
import SupplierForm from './components/SupplierForm';
import SupplierService from '@/services/supplierService';
import type { Supplier, SupplierListParams } from '@/types/supplier';
const { Option } = Select;
const SupplierList: React.FC = () => {
const [form] = Form.useForm();
const [selectedRows, setSelectedRows] = useState<Supplier[]>([]);
const [params, setParams] = useState<SupplierListParams>({
page: 1,
limit: 20,
sortBy: 'createdAt',
sortOrder: 'DESC'
});
const [formVisible, { toggle: toggleForm }] = useToggle(false);
const [detailVisible, { toggle: toggleDetail }] = useToggle(false);
const [selectedSupplier, setSelectedSupplier] = useState<Supplier | null>(null);
// 使用ahooks的useRequest进行数据请求
const { data, loading, run: fetchSuppliers } = useRequest(
() => SupplierService.getList(params),
{
refreshDeps: [params],
onError: (error) => {
message.error(`加载失败: ${error.message}`);
}
}
);
// 列定义
const columns = [
{
title: '供应商编码',
dataIndex: 'code',
key: 'code',
sorter: true,
width: 120
},
{
title: '供应商名称',
dataIndex: 'name',
key: 'name',
render: (text: string, record: Supplier) => (
<a onClick={() => showDetail(record)}>{text}</a>
)
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
render: (type: string) => {
const typeMap: Record<string, { text: string; color: string }> = {
manufacturer: { text: '生产商', color: 'blue' },
distributor: { text: '经销商', color: 'green' },
wholesaler: { text: '批发商', color: 'orange' }
};
const info = typeMap[type] || { text: type, color: 'default' };
return <Tag color={info.color}>{info.text}</Tag>;
}
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => {
const statusMap: Record<string, { text: string; color: string }> = {
active: { text: '活跃', color: 'success' },
inactive: { text: '停用', color: 'default' },
pending: { text: '待审核', color: 'warning' },
blacklisted: { text: '黑名单', color: 'error' }
};
const info = statusMap[status] || { text: status, color: 'default' };
return <Tag color={info.color}>{info.text}</Tag>;
}
},
{
title: '信用评级',
dataIndex: 'creditRating',
key: 'creditRating',
render: (rating: number) => (
<span style={{ color: rating >= 4 ? '#52c41a' : rating >= 3 ? '#faad14' : '#f5222d' }}>
{'★'.repeat(rating)}{'☆'.repeat(5 - rating)}
</span>
)
},
{
title: '操作',
key: 'action',
width: 180,
render: (_: any, record: Supplier) => (
<Space size="small">
<Button
type="text"
icon={<EyeOutlined />}
onClick={() => showDetail(record)}
/>
<Button
type="text"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
/>
<Button
type="text"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(record.id)}
disabled={record.status === 'active'}
/>
</Space>
)
}
];
// 处理表格变化
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
setParams(prev => ({
...prev,
page: pagination.current,
sortBy: sorter.field || 'createdAt',
sortOrder: sorter.order === 'ascend' ? 'ASC' : 'DESC'
}));
};
// 搜索处理
const handleSearch = (values: any) => {
setParams(prev => ({
...prev,
page: 1,
...values
}));
};
// 显示详情
const showDetail = (supplier: Supplier) => {
setSelectedSupplier(supplier);
toggleDetail();
};
// 批量操作
const handleBatchActivate = async () => {
if (selectedRows.length === 0) return;
Modal.confirm({
title: '确认激活选中的供应商?',
content: `将激活 ${selectedRows.length} 个供应商`,
onOk: async () => {
try {
await SupplierService.batchUpdateStatus(
selectedRows.map(r => r.id),
'active'
);
message.success('激活成功');
fetchSuppliers();
setSelectedRows([]);
} catch (error) {
message.error('激活失败');
}
}
});
};
return (
<div className="supplier-list-page">
<Card
title={
<Space>
<span>供应商管理</span>
<Tag color="blue">总数: {data?.total || 0}</Tag>
</Space>
}
extra={
<Space>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={toggleForm}
>
新建供应商
</Button>
</Space>
}
>
{/* 搜索和筛选区域 */}
<Card size="small" style={{ marginBottom: 16 }}>
<Form
form={form}
layout="inline"
onFinish={handleSearch}
>
<Row gutter={[16, 16]} style={{ width: '100%' }}>
<Col xs={24} sm={12} md={8} lg={6}>
<Form.Item name="search">
<Input
placeholder="搜索名称/编码/联系人"
prefix={<SearchOutlined />}
allowClear
/>
</Form.Item>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<Form.Item name="status">
<Select placeholder="状态筛选" allowClear>
<Option value="active">活跃</Option>
<Option value="inactive">停用</Option>
<Option value="pending">待审核</Option>
<Option value="blacklisted">黑名单</Option>
</Select>
</Form.Item>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<Form.Item name="type">
<Select placeholder="类型筛选" allowClear>
<Option value="manufacturer">生产商</Option>
<Option value="distributor">经销商</Option>
<Option value="wholesaler">批发商</Option>
</Select>
</Form.Item>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<Space>
<Button
type="primary"
htmlType="submit"
icon={<FilterOutlined />}
>
筛选
</Button>
<Button
onClick={() => {
form.resetFields();
setParams({
page: 1,
limit: 20,
sortBy: 'createdAt',
sortOrder: 'DESC'
});
}}
>
重置
</Button>
</Space>
</Col>
</Row>
</Form>
</Card>
{/* 批量操作栏 */}
{selectedRows.length > 0 && (
<Card size="small" style={{ marginBottom: 16, background: '#f6ffed' }}>
<Space>
<span>已选择 {selectedRows.length} 项</span>
<Button onClick={() => setSelectedRows([])}>取消选择</Button>
<Button onClick={handleBatchActivate}>批量激活</Button>
<Button danger>批量停用</Button>
</Space>
</Card>
)}
{/* 统计卡片 */}
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
<Col xs={24} sm={12} md={6}>
<Card size="small">
<Statistic
title="活跃供应商"
value={data?.stats?.active || 0}
valueStyle={{ color: '#52c41a' }}
/>
</Card>
</Col>
<Col xs={24} sm={12} md={6}>
<Card size="small">
<Statistic
title="待审核"
value={data?.stats?.pending || 0}
valueStyle={{ color: '#fa8c16' }}
/>
</Card>
</Col>
<Col xs={24} sm={12} md={6}>
<Card size="small">
<Statistic
title="平均信用评级"
value={data?.stats?.avgRating || 0}
precision={1}
/>
</Card>
</Col>
</Row>
{/* 数据表格 */}
<Table
rowKey="id"
columns={columns}
dataSource={data?.data || []}
loading={loading}
pagination={{
current: params.page,
pageSize: params.limit,
total: data?.total || 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `共 ${total} 条记录`
}}
rowSelection={{
selectedRowKeys: selectedRows.map(r => r.id),
onChange: (_, rows) => setSelectedRows(rows)
}}
onChange={handleTableChange}
scroll={{ x: 1200 }}
/>
</Card>
{/* 表单模态框 */}
<Modal
title={selectedSupplier ? '编辑供应商' : '新建供应商'}
open={formVisible}
onCancel={toggleForm}
width={800}
footer={null}
destroyOnClose
>
<SupplierForm
initialValues={selectedSupplier}
onSuccess={() => {
toggleForm();
fetchSuppliers();
setSelectedSupplier(null);
}}
/>
</Modal>
{/* 详情模态框 */}
<Modal
title="供应商详情"
open={detailVisible}
onCancel={toggleDetail}
width={700}
footer={null}
>
{/* 详情组件 */}
</Modal>
</div>
);
};
export default SupplierList;
阶段5:测试用例(3分钟)
// tests/supplier.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/sequelize';
import { SupplierService } from '../src/services/supplier.service';
import { Supplier } from '../src/models/Supplier';
import { ConflictException, NotFoundException } from '@nestjs/common';
describe('SupplierService', () => {
let service: SupplierService;
let mockSupplierModel: any;
beforeEach(async () => {
mockSupplierModel = {
findOne: jest.fn(),
create: jest.fn(),
findAndCountAll: jest.fn(),
findByPk: jest.fn(),
update: jest.fn(),
destroy: jest.fn()
};
const module: TestingModule = await Test.createTestingModule({
providers: [
SupplierService,
{
provide: getModelToken(Supplier),
useValue: mockSupplierModel
}
]
}).compile();
service = module.get<SupplierService>(SupplierService);
});
describe('create', () => {
it('应该成功创建供应商', async () => {
const createDto = {
name: '测试供应商',
code: 'TEST001',
type: 'manufacturer',
contactPerson: '张三',
phone: '13800138000',
email: 'test@example.com'
};
mockSupplierModel.findOne.mockResolvedValue(null);
mockSupplierModel.create.mockResolvedValue({
id: 'uuid-123',
...createDto,
status: 'pending'
});
const result = await service.create(createDto, 'user-123');
expect(result).toHaveProperty('id', 'uuid-123');
expect(result.name).toBe('测试供应商');
expect(mockSupplierModel.create).toHaveBeenCalledWith(
expect.objectContaining({
name: '测试供应商',
createdBy: 'user-123'
})
);
});
it('名称重复时应该抛出冲突异常', async () => {
const createDto = {
name: '重复名称',
code: 'TEST001',
type: 'manufacturer'
};
mockSupplierModel.findOne.mockResolvedValue({
name: '重复名称',
code: 'OTHER001'
});
await expect(service.create(createDto, 'user-123'))
.rejects
.toThrow(ConflictException);
});
});
describe('findAll', () => {
it('应该返回分页数据', async () => {
const mockData = [
{ id: '1', name: '供应商1', status: 'active' },
{ id: '2', name: '供应商2', status: 'active' }
];
mockSupplierModel.findAndCountAll.mockResolvedValue({
count: 2,
rows: mockData
});
const result = await service.findAll({
page: 1,
limit: 20
});
expect(result.data).toHaveLength(2);
expect(result.total).toBe(2);
expect(result.page).toBe(1);
});
it('应该支持搜索功能', async () => {
mockSupplierModel.findAndCountAll.mockResolvedValue({
count: 1,
rows: [{ id: '1', name: '匹配的供应商' }]
});
await service.findAll({
search: '匹配'
});
expect(mockSupplierModel.findAndCountAll).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
[Symbol.for('or')]: expect.arrayContaining([
expect.objectContaining({
name: expect.objectContaining({
[Symbol.for('like')]: '%匹配%'
})
})
])
})
})
);
});
});
});
📊 第六步:生成结果总结
生成文件清单
📁 generated/
├── database/
│ ├── migrations/
│ │ ├── 20240101_create_suppliers_table.sql
│ │ └── 20240101_create_supplier_documents_table.sql
│ └── seeders/
│ └── 20240101_seed_suppliers.sql
├── src/
│ ├── models/
│ │ ├── Supplier.ts
│ │ ├── SupplierDocument.ts
│ │ └── index.ts
│ ├── dto/
│ │ ├── create-supplier.dto.ts
│ │ ├── update-supplier.dto.ts
│ │ └── supplier-response.dto.ts
│ ├── services/
│ │ ├── supplier.service.ts
│ │ └── document.service.ts
│ ├── controllers/
│ │ └── supplier.controller.ts
│ ├── validators/
│ │ └── supplier.validator.ts
│ └── types/
│ └── supplier.ts
├── frontend/
│ ├── pages/
│ │ └── supplier/
│ │ ├── SupplierList.tsx
│ │ ├── SupplierDetail.tsx
│ │ └── components/
│ │ ├── SupplierForm.tsx
│ │ ├── DocumentUpload.tsx
│ │ └── StatusTag.tsx
│ └── services/
│ └── supplierService.ts
├── tests/
│ ├── supplier.service.spec.ts
│ ├── supplier.controller.spec.ts
│ └── e2e/
│ └── suppliers.e2e-spec.ts
├── docs/
│ └── api/
│ └── suppliers.swagger.yaml
└── docker/
├── Dockerfile
└── docker-compose.yml
🎯 第七步:模板使用技巧深度分析
技巧1:分层递进描述
# 第一层:核心需求
实体定义 → 属性列表 → 关联关系
# 第二层:技术栈指定
框架版本 → 数据库类型 → 前端库
# 第三层:质量要求
错误处理 → 性能优化 → 安全考虑
# 第四层:额外功能
文件上传 → 审计日志 → 监控指标
技巧2:约束条件明确化
# 不好的描述
"做一个供应商管理系统"
# 好的描述
"创建供应商CRUD,要求:
1. 支持MySQL软删除
2. 文件上传到S3
3. 操作记录审计日志
4. 接口限流100次/分钟
5. 前端使用Ant Design表格"
技巧3:利用现有代码上下文
# 先引用现有项目结构
@package.json # 了解技术栈
@src/models/User.ts # 参考现有模型风格
@README.md # 了解项目规范
# 再生成新代码
基于现有项目规范,创建Supplier实体...
📈 效率对比分析
| 任务 | 手动开发 | Cursor生成 | 效率提升 |
|---|---|---|---|
| 数据库设计 | 1小时 | 2分钟 | 30倍 |
| 模型定义 | 45分钟 | 1分钟 | 45倍 |
| Service层 | 2小时 | 3分钟 | 40倍 |
| Controller层 | 1.5小时 | 2分钟 | 45倍 |
| 前端组件 | 4小时 | 5分钟 | 48倍 |
| 测试用例 | 2小时 | 3分钟 | 40倍 |
| 总计 | 11.25小时 | 16分钟 | 42倍 |
⚠️ 注意事项
需要人工检查的要点
- 业务逻辑正确性:生成的业务规则是否符合实际需求
- 安全漏洞:检查SQL注入、XSS等安全问题
- 性能优化:生成的查询是否有效率问题
- 架构一致性:是否符合项目整体架构
- 边界条件:异常处理是否完整
建议工作流
使用Cursor生成 → 代码审查 → 单元测试 →
集成测试 → 性能测试 → 部署上线
🚀 进阶用法:模板定制化
创建自己的模板变体
# 我的快速CRUD模板(简版)
生成[实体名]的CRUD:
- 5个核心API
- 基础验证
- 分页查询
- 使用现有项目的代码风格
- 不包含前端界面
# 我的企业级CRUD模板(完整版)
生成[实体名]的企业级CRUD:
- 完整的四层架构
- 审计日志
- 数据权限控制
- 导入导出功能
- 前端管理界面
- 性能监控
- 自动化测试
💡 实战建议
- 从简单开始:先用简单模板,逐步增加复杂度
- 建立模板库:分类保存常用模板
- 持续优化:根据生成结果反馈调整模板
- 团队共享:建立团队模板规范
- 结合文档:为生成的代码添加详细注释
🎁 模板优化挑战
尝试改进这个模板:
当前模板有什么可以优化的地方?
如何让它更适合大型企业项目?
需要添加哪些安全考虑?
你的思考:
- 模板哪些地方可以更详细?
- 哪些地方可以更灵活?
- 如何支持更多技术栈选择?

浙公网安备 33010602011771号