本文目录导读:

我来详细介绍几种在PHP项目中实现交易流水的方法和最佳实践。
数据库设计
基础流水表结构
CREATE TABLE `transaction_flow` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`flow_no` varchar(32) NOT NULL COMMENT '流水号',
`user_id` int(11) NOT NULL COMMENT '用户ID',
`order_no` varchar(32) DEFAULT NULL COMMENT '关联订单号',
`transaction_type` tinyint(4) NOT NULL COMMENT '交易类型:1-充值 2-消费 3-退款 4-提现 5-转账',
`amount` decimal(12,2) NOT NULL COMMENT '交易金额',
`before_balance` decimal(12,2) NOT NULL COMMENT '交易前余额',
`after_balance` decimal(12,2) NOT NULL COMMENT '交易后余额',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:0-失败 1-成功 2-处理中',
`remark` varchar(255) DEFAULT NULL COMMENT '备注说明',
`payment_method` varchar(32) DEFAULT NULL COMMENT '支付方式:wechat/alipay/bank',
`channel_trade_no` varchar(64) DEFAULT NULL COMMENT '第三方交易号',
`ip_address` varchar(15) DEFAULT NULL COMMENT '客户端IP',
`extra_info` text DEFAULT NULL COMMENT '额外信息(JSON)',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_flow_no` (`flow_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_order_no` (`order_no`),
KEY `idx_created_at` (`created_at`),
KEY `idx_type_status` (`transaction_type`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交易流水表';
PHP核心实现
流水号生成器
<?php
class FlowNumberGenerator
{
/**
* 生成唯一流水号
* 格式:前缀 + 时间戳(14位) + 随机数(6位) + 序列号(4位)
*/
public static function generate($prefix = 'FLOW')
{
$dateTime = date('YmdHis');
$random = str_pad(mt_rand(0, 999999), 6, '0', STR_PAD_LEFT);
$sequence = str_pad(mt_rand(0, 9999), 4, '0', STR_PAD_LEFT);
return sprintf('%s%s%s%s', $prefix, $dateTime, $random, $sequence);
}
/**
* 基于Redis生成更可靠的流水号
*/
public static function generateWithRedis($redis, $prefix = 'FLOW')
{
$dateTime = date('YmdHis');
$key = "flow_seq:{$dateTime}";
$sequence = $redis->incr($key);
$redis->expire($key, 2); // 2秒过期
return sprintf('%s%s%04d', $prefix, $dateTime, $sequence);
}
}
交易流水管理器
<?php
class TransactionManager
{
private $db;
private $redis;
public function __construct($db, $redis = null)
{
$this->db = $db;
$this->redis = $redis;
}
/**
* 创建交易记录
*/
public function createTransaction(array $data)
{
try {
$this->db->beginTransaction();
// 锁定用户账户
$user = $this->lockUserAccount($data['user_id']);
// 验证并计算余额
$balanceResult = $this->calculateBalance($user, $data);
if (!$balanceResult['success']) {
throw new \Exception($balanceResult['message']);
}
// 生成流水号
$flowNo = $this->redis ?
FlowNumberGenerator::generateWithRedis($this->redis) :
FlowNumberGenerator::generate();
// 插入流水记录
$flowData = [
'flow_no' => $flowNo,
'user_id' => $data['user_id'],
'order_no' => $data['order_no'] ?? null,
'transaction_type' => $data['transaction_type'],
'amount' => $data['amount'],
'before_balance' => $balanceResult['before_balance'],
'after_balance' => $balanceResult['after_balance'],
'status' => 1, // 初始状态
'remark' => $data['remark'] ?? '',
'payment_method' => $data['payment_method'] ?? '',
'ip_address' => $data['ip_address'] ?? '',
'extra_info' => json_encode($data['extra_info'] ?? [])
];
$this->insertTransactionFlow($flowData);
// 更新用户余额
$this->updateUserBalance($data['user_id'], $balanceResult['after_balance']);
$this->db->commit();
// 记录日志
$this->logTransaction($flowNo, 'success');
return [
'success' => true,
'flow_no' => $flowNo,
'message' => '交易成功'
];
} catch (\Exception $e) {
$this->db->rollBack();
$this->logTransaction($flowNo ?? 'unknown', 'failed', $e->getMessage());
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* 锁定用户账户
*/
private function lockUserAccount($userId)
{
$sql = "SELECT * FROM users WHERE id = ? FOR UPDATE";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId]);
return $stmt->fetch(\PDO::FETCH_ASSOC);
}
/**
* 计算余额
*/
private function calculateBalance($user, $data)
{
$currentBalance = (float)$user['balance'];
$amount = (float)$data['amount'];
switch ($data['transaction_type']) {
case 1: // 充值
case 5: // 转入
$newBalance = $currentBalance + $amount;
break;
case 2: // 消费
case 4: // 提现
if ($currentBalance < $amount) {
return ['success' => false, 'message' => '余额不足'];
}
$newBalance = $currentBalance - $amount;
break;
case 3: // 退款
$newBalance = $currentBalance + $amount;
break;
default:
return ['success' => false, 'message' => '无效的交易类型'];
}
return [
'success' => true,
'before_balance' => $currentBalance,
'after_balance' => $newBalance
];
}
/**
* 查询交易流水
*/
public function queryTransactions($userId, $filters = [], $page = 1, $pageSize = 20)
{
$where = ['user_id = ?'];
$params = [$userId];
// 添加筛选条件
if (!empty($filters['type'])) {
$where[] = 'transaction_type = ?';
$params[] = (int)$filters['type'];
}
if (!empty($filters['start_time'])) {
$where[] = 'created_at >= ?';
$params[] = $filters['start_time'];
}
if (!empty($filters['end_time'])) {
$where[] = 'created_at <= ?';
$params[] = $filters['end_time'];
}
if (isset($filters['status'])) {
$where[] = 'status = ?';
$params[] = (int)$filters['status'];
}
$whereClause = implode(' AND ', $where);
$offset = ($page - 1) * $pageSize;
// 查询总数
$countSql = "SELECT COUNT(*) FROM transaction_flow WHERE {$whereClause}";
$countStmt = $this->db->prepare($countSql);
$countStmt->execute($params);
$total = $countStmt->fetchColumn();
// 查询记录
$sql = "SELECT * FROM transaction_flow WHERE {$whereClause}
ORDER BY created_at DESC LIMIT ? OFFSET ?";
$params[] = $pageSize;
$params[] = $offset;
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
$list = $stmt->fetchAll(\PDO::FETCH_ASSOC);
return [
'total' => $total,
'page' => $page,
'pageSize' => $pageSize,
'list' => $list
];
}
/**
* 日志记录
*/
private function logTransaction($flowNo, $status, $message = '')
{
// 实现日志记录
error_log("Transaction: {$flowNo}, Status: {$status}, Message: {$message}");
}
}
控制器示例
<?php
class TransactionController
{
private $transactionManager;
public function __construct($db, $redis)
{
$this->transactionManager = new TransactionManager($db, $redis);
}
/**
* 创建充值交易
*/
public function recharge(Request $request)
{
// 验证参数
$validator = new Validator($request->all(), [
'user_id' => 'required|integer',
'amount' => 'required|numeric|min:0.01',
'payment_method' => 'required|in:wechat,alipay,bank'
]);
if ($validator->fails()) {
return response()->json(['error' => $validator->errors()], 400);
}
// 创建交易
$result = $this->transactionManager->createTransaction([
'user_id' => $request->input('user_id'),
'amount' => $request->input('amount'),
'transaction_type' => 1,
'payment_method' => $request->input('payment_method'),
'remark' => '用户充值',
'ip_address' => $request->getClientIp(),
'order_no' => $request->input('order_no')
]);
return response()->json($result);
}
/**
* 查询交易记录
*/
public function query(Request $request)
{
$result = $this->transactionManager->queryTransactions(
$request->input('user_id'),
$request->only(['type', 'start_time', 'end_time', 'status']),
$request->input('page', 1),
$request->input('page_size', 20)
);
return response()->json($result);
}
}
最佳实践建议
安全性考虑
- 使用数据库事务保证数据一致性
- 添加行级锁避免并发问题
- 重要操作记录操作日志
- 敏感数据加密存储
性能优化
- 建立合适的索引
- 使用分表策略(按月或按用户ID)
- 定期归档历史数据
- 使用缓存减少数据库查询
扩展功能
// 添加交易对账功能
trait ReconciliationTrait
{
public function reconcile($date)
{
// 与第三方支付对账
// 核对流水一致性
// 处理异常记录
}
}
// 添加统计分析
trait StatisticsTrait
{
public function getStatistics($userId, $startTime, $endTime)
{
// 计算总交易额
// 统计交易类型分布
// 分析交易趋势
}
}
这个实现方案包含了完整的交易流水处理逻辑,可以根据具体业务需求进行调整和扩展,记得在实际部署时要做好数据备份和监控告警。