Claude Code实战:5个真实业务场景的完整解决方案
理论看得再多,不如实战来得实在。这篇文章带你解决5个真实的业务场景,从问题分析到完整实现,每个都是可以直接用在项目中的解决方案。
场景1:数据导入导出系统
业务需求
电商后台需要支持:
- 批量导入商品(Excel/CSV)
- 数据验证和清洗
- 导入进度显示
- 错误提示和部分导入
- 导出报表(支持筛选和格式化)
痛点
- 大文件处理(10万+行数据)
- 异步处理避免阻塞
- 错误处理和回滚
- 进度反馈
完整实现
第一步:后端API设计
你:"设计商品导入导出系统:
后端需求:
1. 文件上传接口(支持Excel/CSV,最大10MB)
2. 异步数据处理(使用队列)
3. 数据验证(商品名、价格、库存等)
4. 批量数据库写入(事务处理)
5. 导入进度查询接口
6. 导出接口(支持筛选和分页)
技术栈:Node.js + Bull队列 + Prisma + ExcelJS"
Claude Code:
✓ 创建文件上传接口
✓ 集成Bull队列系统
✓ 实现数据验证器
✓ 创建批量导入processor
✓ 实现进度追踪
✓ 创建导出接口
核心代码:
// src/services/import.service.ts
import { Queue } from 'bull';
import ExcelJS from 'exceljs';
import { z } from 'zod';
// 商品验证schema
const productSchema = z.object({
name: z.string().min(1).max(200),
sku: z.string().min(1),
price: z.number().positive(),
stock: z.number().int().min(0),
category: z.string(),
description: z.string().optional(),
});
export class ImportService {
private importQueue: Queue;
constructor() {
this.importQueue = new Queue('product-import', {
redis: process.env.REDIS_URL,
});
this.setupProcessor();
}
// 处理文件上传
async handleUpload(file: Express.Multer.File, userId: string) {
// 解析Excel/CSV
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(file.buffer);
const worksheet = workbook.worksheets[0];
// 提取数据
const rows = [];
worksheet.eachRow((row, rowIndex) => {
if (rowIndex === 1) return; // 跳过表头
rows.push({
name: row.getCell(1).value,
sku: row.getCell(2).value,
price: Number(row.getCell(3).value),
stock: Number(row.getCell(4).value),
category: row.getCell(5).value,
description: row.getCell(6).value,
});
});
// 创建导入任务
const job = await this.importQueue.add({
userId,
products: rows,
fileName: file.originalname,
});
return {
jobId: job.id,
totalItems: rows.length,
};
}
// 设置队列处理器
private setupProcessor() {
this.importQueue.process(async (job) => {
const { userId, products } = job.data;
const results = {
success: 0,
failed: 0,
errors: [],
};
// 分批处理(每批100条)
const batchSize = 100;
for (let i = 0; i < products.length; i += batchSize) {
const batch = products.slice(i, i + batchSize);
// 验证和处理
const validProducts = [];
for (const product of batch) {
try {
const validated = productSchema.parse(product);
validProducts.push(validated);
} catch (error) {
results.failed++;
results.errors.push({
row: i + batch.indexOf(product) + 2,
data: product,
error: error.errors,
});
}
}
// 批量插入数据库
if (validProducts.length > 0) {
try {
await prisma.product.createMany({
data: validProducts.map(p => ({
...p,
userId,
})),
skipDuplicates: true,
});
results.success += validProducts.length;
} catch (error) {
results.failed += validProducts.length;
results.errors.push({
batch: i / batchSize + 1,
error: error.message,
});
}
}
// 更新进度
await job.progress((i + batchSize) / products.length * 100);
}
return results;
});
}
// 查询导入进度
async getJobStatus(jobId: string) {
const job = await this.importQueue.getJob(jobId);
if (!job) {
throw new Error('Job not found');
}
const state = await job.getState();
const progress = job.progress();
const result = job.returnvalue;
return {
state,
progress,
result,
};
}
// 导出商品
async exportProducts(filter: any) {
const products = await prisma.product.findMany({
where: filter,
});
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Products');
// 设置表头
worksheet.columns = [
{ header: '商品名称', key: 'name', width: 30 },
{ header: 'SKU', key: 'sku', width: 20 },
{ header: '价格', key: 'price', width: 15 },
{ header: '库存', key: 'stock', width: 15 },
{ header: '分类', key: 'category', width: 20 },
{ header: '描述', key: 'description', width: 50 },
];
// 添加数据
worksheet.addRows(products);
// 样式设置
worksheet.getRow(1).font = { bold: true };
worksheet.getRow(1).fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFE0E0E0' },
};
const buffer = await workbook.xlsx.writeBuffer();
return buffer;
}
}
API路由:
// src/routes/import.routes.ts
import multer from 'multer';
import { ImportService } from '../services/import.service';
const router = express.Router();
const upload = multer({
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
fileFilter: (req, file, cb) => {
if (file.mimetype === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
file.mimetype === 'text/csv') {
cb(null, true);
} else {
cb(new Error('Only Excel and CSV files are allowed'));
}
},
});
const importService = new ImportService();
// 上传文件开始导入
router.post('/import', authenticate, upload.single('file'), async (req, res) => {
try {
const result = await importService.handleUpload(req.file, req.user.id);
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 查询导入进度
router.get('/import/:jobId', authenticate, async (req, res) => {
try {
const status = await importService.getJobStatus(req.params.jobId);
res.json(status);
} catch (error) {
res.status(404).json({ error: error.message });
}
});
// 导出商品
router.get('/export', authenticate, async (req, res) => {
try {
const buffer = await importService.exportProducts(req.query);
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', 'attachment; filename=products.xlsx');
res.send(buffer);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
export default router;
第二步:前端实现
你:"创建前端导入导出界面:
1. 文件上传组件(拖拽支持)
2. 进度条显示
3. 错误列表展示
4. 导出按钮和筛选
使用React + Ant Design"
Claude Code:
✓ 创建上传组件
✓ 实现进度轮询
✓ 错误信息展示
✓ 导出功能
前端组件:
// components/ProductImport.tsx
import { useState } from 'react';
import { Upload, Progress, Button, Table, Alert } from 'antd';
import { InboxOutlined } from '@ant-design/icons';
export default function ProductImport() {
const [uploading, setUploading] = useState(false);
const [jobId, setJobId] = useState(null);
const [progress, setProgress] = useState(0);
const [result, setResult] = useState(null);
// 文件上传
const handleUpload = async (file) => {
const formData = new FormData();
formData.append('file', file);
setUploading(true);
try {
const response = await fetch('/api/import', {
method: 'POST',
headers: {
Authorization: `Bearer ${getToken()}`,
},
body: formData,
});
const data = await response.json();
setJobId(data.jobId);
// 开始轮询进度
pollProgress(data.jobId);
} catch (error) {
message.error('上传失败');
setUploading(false);
}
return false; // 阻止默认上传
};
// 轮询进度
const pollProgress = async (jobId) => {
const interval = setInterval(async () => {
try {
const response = await fetch(`/api/import/${jobId}`, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
});
const status = await response.json();
setProgress(status.progress);
if (status.state === 'completed') {
clearInterval(interval);
setResult(status.result);
setUploading(false);
message.success('导入完成');
} else if (status.state === 'failed') {
clearInterval(interval);
setUploading(false);
message.error('导入失败');
}
} catch (error) {
clearInterval(interval);
setUploading(false);
}
}, 1000);
};
return (
<div className="product-import">
<Upload.Dragger
beforeUpload={handleUpload}
accept=".xlsx,.csv"
showUploadList={false}
disabled={uploading}
>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
<p className="ant-upload-hint">支持Excel和CSV格式,最大10MB</p>
</Upload.Dragger>
{uploading && (
<div className="mt-4">
<Progress percent={Math.round(progress)} status="active" />
<p className="text-center mt-2">正在导入...</p>
</div>
)}
{result && (
<div className="mt-4">
<Alert
message={`导入完成:成功 ${result.success} 条,失败 ${result.failed} 条`}
type={result.failed > 0 ? 'warning' : 'success'}
showIcon
/>
{result.errors.length > 0 && (
<Table
className="mt-4"
dataSource={result.errors}
columns={[
{ title: '行号', dataIndex: 'row', key: 'row' },
{ title: '错误信息', dataIndex: 'error', key: 'error',
render: (errors) => JSON.stringify(errors) },
]}
pagination={{ pageSize: 10 }}
/>
)}
</div>
)}
</div>
);
}
成果
- ✅ 支持大文件异步处理(10万+行)
- ✅ 实时进度反馈
- ✅ 详细错误提示
- ✅ 部分导入(跳过错误行)
- ✅ 灵活的导出功能
场景2:实时通知系统
业务需求
应用需要实时推送通知:
- 订单状态更新
- 系统消息
- 用户互动(评论、点赞)
- 多设备同步
- 未读计数
技术选型
WebSocket + Redis Pub/Sub + 数据库持久化
完整实现
你:"实现完整的实时通知系统:
后端:
1. WebSocket服务器(Socket.io)
2. Redis Pub/Sub(多实例消息同步)
3. 通知持久化(数据库)
4. 未读计数
5. 历史通知查询
前端:
1. WebSocket连接管理
2. 通知展示(Toast + 列表)
3. 未读标记
4. 自动重连"
Claude Code:生成完整实现
后端实现:
// src/services/notification.service.ts
import { Server as SocketServer } from 'socket.io';
import Redis from 'ioredis';
import { prisma } from './prisma';
export class NotificationService {
private io: SocketServer;
private redis: Redis;
private redisSub: Redis;
constructor(io: SocketServer) {
this.io = io;
this.redis = new Redis(process.env.REDIS_URL);
this.redisSub = new Redis(process.env.REDIS_URL);
this.setupSocketHandlers();
this.setupRedisSubscription();
}
// Socket.io连接处理
private setupSocketHandlers() {
this.io.use(async (socket, next) => {
const token = socket.handshake.auth.token;
try {
const user = await verifyToken(token);
socket.data.userId = user.id;
next();
} catch (error) {
next(new Error('Authentication failed'));
}
});
this.io.on('connection', (socket) => {
const userId = socket.data.userId;
console.log(`User ${userId} connected`);
// 加入用户专属房间
socket.join(`user:${userId}`);
// 发送未读计数
this.sendUnreadCount(userId);
// 标记已读
socket.on('markRead', async (notificationIds: string[]) => {
await this.markAsRead(userId, notificationIds);
this.sendUnreadCount(userId);
});
// 获取历史通知
socket.on('getNotifications', async ({ page = 1, limit = 20 }) => {
const notifications = await this.getNotifications(userId, page, limit);
socket.emit('notifications', notifications);
});
socket.on('disconnect', () => {
console.log(`User ${userId} disconnected`);
});
});
}
// Redis订阅(用于多实例消息同步)
private setupRedisSubscription() {
this.redisSub.subscribe('notifications');
this.redisSub.on('message', (channel, message) => {
if (channel === 'notifications') {
const notification = JSON.parse(message);
this.sendToUser(notification.userId, notification);
}
});
}
// 创建并发送通知
async createNotification(data: {
userId: string;
type: string;
title: string;
content: string;
link?: string;
metadata?: any;
}) {
// 保存到数据库
const notification = await prisma.notification.create({
data: {
...data,
read: false,
},
});
// 发布到Redis(供其他实例接收)
await this.redis.publish('notifications', JSON.stringify(notification));
// 发送给用户
this.sendToUser(data.userId, notification);
return notification;
}
// 发送给特定用户
private sendToUser(userId: string, notification: any) {
this.io.to(`user:${userId}`).emit('notification', notification);
}
// 批量通知
async notifyMultipleUsers(userIds: string[], data: Omit<Notification, 'userId'>) {
const notifications = await prisma.notification.createMany({
data: userIds.map(userId => ({
...data,
userId,
read: false,
})),
});
userIds.forEach(userId => {
this.redis.publish('notifications', JSON.stringify({
...data,
userId,
}));
});
}
// 标记已读
private async markAsRead(userId: string, notificationIds: string[]) {
await prisma.notification.updateMany({
where: {
id: { in: notificationIds },
userId,
},
data: { read: true },
});
}
// 获取未读计数
private async sendUnreadCount(userId: string) {
const count = await prisma.notification.count({
where: {
userId,
read: false,
},
});
this.io.to(`user:${userId}`).emit('unreadCount', count);
}
// 获取历史通知
private async getNotifications(userId: string, page: number, limit: number) {
const notifications = await prisma.notification.findMany({
where: { userId },
orderBy: { createdAt: 'desc' },
skip: (page - 1) * limit,
take: limit,
});
return notifications;
}
}
前端Hook:
// hooks/useNotifications.ts
import { useEffect, useState } from 'react';
import { io, Socket } from 'socket.io-client';
import { message } from 'antd';
interface Notification {
id: string;
type: string;
title: string;
content: string;
link?: string;
read: boolean;
createdAt: string;
}
export function useNotifications() {
const [socket, setSocket] = useState<Socket | null>(null);
const [notifications, setNotifications] = useState<Notification[]>([]);
const [unreadCount, setUnreadCount] = useState(0);
const [connected, setConnected] = useState(false);
useEffect(() => {
const token = localStorage.getItem('token');
if (!token) return;
// 连接Socket.io
const newSocket = io(process.env.NEXT_PUBLIC_WS_URL!, {
auth: { token },
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5,
});
newSocket.on('connect', () => {
setConnected(true);
console.log('Notification service connected');
});
newSocket.on('disconnect', () => {
setConnected(false);
console.log('Notification service disconnected');
});
// 接收新通知
newSocket.on('notification', (notification: Notification) => {
setNotifications(prev => [notification, ...prev]);
// 显示Toast
message.info({
content: notification.title,
duration: 3,
onClick: () => {
if (notification.link) {
window.location.href = notification.link;
}
},
});
});
// 更新未读计数
newSocket.on('unreadCount', (count: number) => {
setUnreadCount(count);
});
// 接收历史通知
newSocket.on('notifications', (data: Notification[]) => {
setNotifications(data);
});
setSocket(newSocket);
return () => {
newSocket.close();
};
}, []);
// 标记已读
const markAsRead = (notificationIds: string[]) => {
if (socket) {
socket.emit('markRead', notificationIds);
setNotifications(prev =>
prev.map(n =>
notificationIds.includes(n.id) ? { ...n, read: true } : n
)
);
}
};
// 加载更多
const loadMore = (page: number) => {
if (socket) {
socket.emit('getNotifications', { page, limit: 20 });
}
};
return {
notifications,
unreadCount,
connected,
markAsRead,
loadMore,
};
}
UI组件:
// components/NotificationBell.tsx
import { Badge, Dropdown, List, Button } from 'antd';
import { BellOutlined } from '@ant-design/icons';
import { useNotifications } from '../hooks/useNotifications';
export default function NotificationBell() {
const { notifications, unreadCount, markAsRead } = useNotifications();
const menu = (
<div className="w-96 max-h-96 overflow-auto bg-white rounded shadow-lg">
<div className="p-4 border-b flex justify-between items-center">
<span className="font-semibold">通知</span>
{unreadCount > 0 && (
<Button
type="link"
size="small"
onClick={() => markAsRead(notifications.filter(n => !n.read).map(n => n.id))}
>
全部已读
</Button>
)}
</div>
<List
dataSource={notifications.slice(0, 10)}
renderItem={(notification) => (
<List.Item
className={`px-4 hover:bg-gray-50 cursor-pointer ${
!notification.read ? 'bg-blue-50' : ''
}`}
onClick={() => {
markAsRead([notification.id]);
if (notification.link) {
window.location.href = notification.link;
}
}}
>
<List.Item.Meta
title={notification.title}
description={
<>
<div>{notification.content}</div>
<div className="text-xs text-gray-400 mt-1">
{formatTime(notification.createdAt)}
</div>
</>
}
/>
</List.Item>
)}
/>
</div>
);
return (
<Dropdown overlay={menu} trigger={['click']} placement="bottomRight">
<Badge count={unreadCount} offset={[-5, 5]}>
<BellOutlined className="text-xl cursor-pointer" />
</Badge>
</Dropdown>
);
}
成果
- ✅ 实时推送(<100ms延迟)
- ✅ 多设备同步
- ✅ 消息持久化
- ✅ 自动重连
- ✅ 优雅的UI交互
场景3:权限管理系统(RBAC)
业务需求
复杂的权限管理:
- 角色-权限模型(RBAC)
- 细粒度权限(资源级别)
- 权限继承
- 动态权限检查
数据模型设计
// prisma/schema.prisma
model User {
id String @id @default(cuid())
email String @unique
name String
roles Role[]
permissions Permission[] // 用户特殊权限
}
model Role {
id String @id @default(cuid())
name String @unique
description String?
permissions Permission[]
users User[]
}
model Permission {
id String @id @default(cuid())
resource String // 资源类型:post, user, order
action String // 操作:read, create, update, delete
condition Json? // 条件:{ "field": "authorId", "operator": "eq", "value": "$userId" }
roles Role[]
users User[]
@@unique([resource, action])
}
权限检查中间件
// middleware/permission.middleware.ts
export function requirePermission(resource: string, action: string) {
return async (req: Request, res: Response, next: NextFunction) => {
const user = req.user;
// 获取用户所有权限(包括角色权限)
const userWithPermissions = await prisma.user.findUnique({
where: { id: user.id },
include: {
permissions: true,
roles: {
include: {
permissions: true,
},
},
},
});
// 合并所有权限
const allPermissions = [
...userWithPermissions.permissions,
...userWithPermissions.roles.flatMap(role => role.permissions),
];
// 检查是否有对应权限
const permission = allPermissions.find(
p => p.resource === resource && p.action === action
);
if (!permission) {
return res.status(403).json({ error: 'Permission denied' });
}
// 检查条件权限
if (permission.condition) {
const allowed = await checkCondition(
permission.condition,
user,
req.params,
req.body
);
if (!allowed) {
return res.status(403).json({ error: 'Condition not met' });
}
}
next();
};
}
// 条件检查
async function checkCondition(
condition: any,
user: any,
params: any,
body: any
) {
const { field, operator, value } = condition;
// 获取资源数据
const resourceId = params.id;
const resource = await prisma[condition.resource].findUnique({
where: { id: resourceId },
});
if (!resource) return false;
// 替换变量
const expectedValue = value.replace('$userId', user.id);
// 执行比较
switch (operator) {
case 'eq':
return resource[field] === expectedValue;
case 'ne':
return resource[field] !== expectedValue;
case 'in':
return expectedValue.includes(resource[field]);
default:
return false;
}
}
使用示例
// routes/posts.routes.ts
import { requirePermission } from '../middleware/permission.middleware';
const router = express.Router();
// 任何人可以读文章
router.get('/posts', requirePermission('post', 'read'), async (req, res) => {
const posts = await prisma.post.findMany();
res.json(posts);
});
// 只有作者可以更新自己的文章
router.put('/posts/:id',
requirePermission('post', 'update'), // 权限定义:{ resource: 'post', action: 'update', condition: { field: 'authorId', operator: 'eq', value: '$userId' } }
async (req, res) => {
const post = await prisma.post.update({
where: { id: req.params.id },
data: req.body,
});
res.json(post);
}
);
// 只有管理员可以删除任何文章
router.delete('/posts/:id',
requirePermission('post', 'delete'), // 只有admin角色有此权限
async (req, res) => {
await prisma.post.delete({ where: { id: req.params.id } });
res.json({ success: true });
}
);
前端权限检查
// hooks/usePermission.ts
export function usePermission() {
const { user } = useAuth();
const hasPermission = (resource: string, action: string) => {
return user?.permissions?.some(
p => p.resource === resource && p.action === action
) || false;
};
return { hasPermission };
}
// 使用
function PostActions({ post }) {
const { hasPermission } = usePermission();
const { user } = useAuth();
const canEdit = hasPermission('post', 'update') && post.authorId === user.id;
const canDelete = hasPermission('post', 'delete');
return (
<>
{canEdit && <Button onClick={handleEdit}>编辑</Button>}
{canDelete && <Button onClick={handleDelete}>删除</Button>}
</>
);
}
场景4:搜索优化(全文搜索+自动补全)
需求
- 快速全文搜索(支持中文)
- 搜索建议(自动补全)
- 搜索高亮
- 相关性排序
- 搜索历史
技术选型
PostgreSQL全文搜索 + Redis缓存
实现
// services/search.service.ts
export class SearchService {
// 创建全文搜索索引
async setupSearchIndex() {
await prisma.$executeRaw`
ALTER TABLE products
ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('chinese_zh', coalesce(name, '')), 'A') ||
setweight(to_tsvector('chinese_zh', coalesce(description, '')), 'B') ||
setweight(to_tsvector('chinese_zh', coalesce(category, '')), 'C')
) STORED;
CREATE INDEX products_search_idx ON products USING GIN (search_vector);
`;
}
// 搜索
async search(query: string, options: SearchOptions) {
const { page = 1, limit = 20, filters = {} } = options;
// 构建搜索查询
const results = await prisma.$queryRaw`
SELECT
id,
name,
description,
price,
ts_rank(search_vector, websearch_to_tsquery('chinese_zh', ${query})) as rank,
ts_headline('chinese_zh', name, websearch_to_tsquery('chinese_zh', ${query})) as highlighted_name
FROM products
WHERE search_vector @@ websearch_to_tsquery('chinese_zh', ${query})
${filters.category ? Prisma.sql`AND category = ${filters.category}` : Prisma.empty}
${filters.minPrice ? Prisma.sql`AND price >= ${filters.minPrice}` : Prisma.empty}
ORDER BY rank DESC
LIMIT ${limit}
OFFSET ${(page - 1) * limit}
`;
// 保存搜索历史
await this.saveSearchHistory(query);
return results;
}
// 自动补全
async autocomplete(query: string, limit = 10) {
// 先从Redis缓存获取
const cacheKey = `autocomplete:${query}`;
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 数据库查询
const suggestions = await prisma.product.findMany({
where: {
name: {
contains: query,
mode: 'insensitive',
},
},
select: {
name: true,
},
distinct: ['name'],
take: limit,
});
const results = suggestions.map(s => s.name);
// 缓存1小时
await redis.setex(cacheKey, 3600, JSON.stringify(results));
return results;
}
// 热门搜索
async getHotSearches(limit = 10) {
const cacheKey = 'hot_searches';
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
const hot = await prisma.searchHistory.groupBy({
by: ['query'],
_count: { query: true },
orderBy: { _count: { query: 'desc' } },
take: limit,
where: {
createdAt: {
gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 最近7天
},
},
});
const results = hot.map(h => h.query);
await redis.setex(cacheKey, 3600, JSON.stringify(results));
return results;
}
}
前端实现
// components/SearchBar.tsx
import { useState, useEffect } from 'react';
import { AutoComplete, Input } from 'antd';
import { useDebounce } from '../hooks/useDebounce';
export default function SearchBar({ onSearch }) {
const [query, setQuery] = useState('');
const [suggestions, setSuggestions] = useState([]);
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery.length >= 2) {
fetchSuggestions(debouncedQuery);
}
}, [debouncedQuery]);
const fetchSuggestions = async (q: string) => {
const res = await fetch(`/api/search/autocomplete?q=${q}`);
const data = await res.json();
setSuggestions(data.map(text => ({ value: text })));
};
return (
<AutoComplete
options={suggestions}
onSelect={onSearch}
onSearch={setQuery}
value={query}
>
<Input.Search
placeholder="搜索商品"
onSearch={onSearch}
enterButton
size="large"
/>
</AutoComplete>
);
}
场景5:支付集成(多渠道支付)
需求
- 支持多种支付方式(微信、支付宝、银行卡)
- 支付状态管理
- 异步回调处理
- 订单幂等性
- 退款功能
架构设计
// services/payment/PaymentStrategy.ts
export abstract class PaymentStrategy {
abstract createPayment(order: Order): Promise<PaymentResult>;
abstract handleCallback(data: any): Promise<CallbackResult>;
abstract refund(paymentId: string, amount: number): Promise<RefundResult>;
abstract queryStatus(paymentId: string): Promise<PaymentStatus>;
}
// services/payment/WechatPayment.ts
export class WechatPayment extends PaymentStrategy {
async createPayment(order: Order): Promise<PaymentResult> {
// 调用微信支付API
const result = await wechatpay.createOrder({
out_trade_no: order.id,
amount: { total: order.total },
description: order.description,
notify_url: `${process.env.API_URL}/webhooks/wechat`,
});
return {
paymentId: result.prepay_id,
paymentUrl: result.code_url,
extra: result,
};
}
async handleCallback(data: any): Promise<CallbackResult> {
// 验证签名
const verified = this.verifySignature(data);
if (!verified) {
throw new Error('Invalid signature');
}
return {
orderId: data.out_trade_no,
paymentId: data.transaction_id,
status: data.trade_state === 'SUCCESS' ? 'paid' : 'failed',
paidAt: new Date(data.time_end),
};
}
async refund(paymentId: string, amount: number): Promise<RefundResult> {
// 微信退款逻辑
}
}
// 支付宝、银行卡等其他支付方式类似实现...
支付服务
// services/payment.service.ts
export class PaymentService {
private strategies: Map<string, PaymentStrategy>;
constructor() {
this.strategies = new Map([
['wechat', new WechatPayment()],
['alipay', new AlipayPayment()],
['card', new CardPayment()],
]);
}
async createPayment(orderId: string, method: string) {
// 获取订单
const order = await prisma.order.findUnique({
where: { id: orderId },
});
if (!order) {
throw new Error('Order not found');
}
// 检查订单状态(幂等性)
if (order.status !== 'pending') {
throw new Error('Order already processed');
}
// 选择支付策略
const strategy = this.strategies.get(method);
if (!strategy) {
throw new Error('Invalid payment method');
}
// 创建支付
const result = await strategy.createPayment(order);
// 更新订单状态
await prisma.order.update({
where: { id: orderId },
data: {
status: 'paying',
paymentMethod: method,
paymentId: result.paymentId,
},
});
return result;
}
async handleCallback(method: string, data: any) {
const strategy = this.strategies.get(method);
if (!strategy) {
throw new Error('Invalid payment method');
}
// 处理回调
const result = await strategy.handleCallback(data);
// 使用事务更新订单(保证幂等性)
await prisma.$transaction(async (tx) => {
const order = await tx.order.findUnique({
where: { id: result.orderId },
});
if (order.status === 'paid') {
// 已支付,跳过(幂等性)
return;
}
// 更新订单状态
await tx.order.update({
where: { id: result.orderId },
data: {
status: result.status,
paidAt: result.paidAt,
},
});
// 如果支付成功,执行后续业务逻辑
if (result.status === 'paid') {
await this.onPaymentSuccess(order, tx);
}
});
return result;
}
private async onPaymentSuccess(order: Order, tx: any) {
// 扣减库存
for (const item of order.items) {
await tx.product.update({
where: { id: item.productId },
data: {
stock: { decrement: item.quantity },
},
});
}
// 发送通知
await notificationService.createNotification({
userId: order.userId,
type: 'order_paid',
title: '支付成功',
content: `订单 ${order.id} 支付成功`,
link: `/orders/${order.id}`,
});
// 其他业务逻辑...
}
}
5个场景的共同经验
1. 分层架构
所有场景都采用清晰的分层:
- Controller层:处理HTTP请求
- Service层:业务逻辑
- Repository层:数据访问
- Model层:数据模型
2. 错误处理
统一的错误处理模式:
try {
// 业务逻辑
} catch (error) {
logger.error(error);
if (error instanceof BusinessError) {
return res.status(400).json({ error: error.message });
}
return res.status(500).json({ error: 'Internal server error' });
}
3. 性能优化
- 数据库查询优化(索引、N+1问题)
- Redis缓存热点数据
- 异步处理耗时操作(队列)
- 分页加载大数据
4. 安全性
- 输入验证(Zod)
- 认证鉴权(JWT)
- SQL注入防护(Prisma)
- XSS防护(输入转义)
5. 测试覆盖
- 单元测试(业务逻辑)
- 集成测试(API端点)
- E2E测试(关键流程)
总结
这5个场景覆盖了大多数业务系统的核心功能:
- 数据处理(导入导出)
- 实时通信(通知系统)
- 权限控制(RBAC)
- 搜索功能(全文搜索)
- 支付集成(多渠道支付)
每个都是生产级别的实现,可以直接用在真实项目中。
关键是理解底层原理,然后根据自己的业务需求调整。Claude Code可以帮你快速实现,但架构设计和业务逻辑还需要你把控。