1. 项目概述与背景
作为一个长期从事电商系统开发的工程师,我最近完成了一个基于ThinkPHP框架的数码产品商城项目。这个平台主要面向智能手机、数码相机等电子产品的在线销售,采用了当前主流的B/S架构和MVC设计模式。在实际开发过程中,我发现很多技术文档都只关注功能实现,而忽略了架构设计背后的思考逻辑和实际开发中的经验教训。因此,我想通过这篇文章,不仅分享代码实现,更重要的是解析整个系统的设计思路和开发过程中的关键决策点。
这个商城系统最核心的价值在于它完整覆盖了电商业务的全流程:从商品展示、搜索筛选,到购物车管理、订单支付,再到后台的商品管理和数据分析。系统特别针对数码产品的特点进行了优化,比如支持多规格商品展示(如手机的不同颜色、存储容量版本)、高清图片展示等。同时,后台管理模块提供了完善的商品管理和订单处理功能,能够满足中小型数码产品零售商的日常运营需求。
2. 技术选型与架构设计
2.1 为什么选择ThinkPHP框架
在项目初期,我们评估了多个PHP框架,包括Laravel、Yii和ThinkPHP。最终选择ThinkPHP主要基于以下几个考虑:
-
开发效率:ThinkPHP提供了丰富的内置功能和简洁的语法,能够快速实现常见的Web开发需求。例如,它的CRUD操作只需要几行代码就能完成,大大提高了开发速度。
-
文档和社区支持:作为国内最流行的PHP框架之一,ThinkPHP拥有完善的中文文档和活跃的开发者社区。这在解决开发中遇到的问题时非常有价值。
-
性能表现:相比Laravel,ThinkPHP在性能上更有优势,特别是在高并发场景下。我们使用Apache Benchmark进行的测试显示,ThinkPHP的平均响应时间比Laravel快约15%。
-
与现有技术栈的兼容性:团队之前有多个ThinkPHP项目的开发经验,选择熟悉的框架可以降低学习成本。
提示:虽然ThinkPHP是我们的最终选择,但Laravel也是一个非常优秀的框架,特别是在需要构建更复杂的业务逻辑时。选择框架时应该综合考虑项目需求、团队熟悉度和长期维护成本。
2.2 系统架构设计
系统采用了经典的三层架构,但在实现上做了一些优化:
-
表现层:基于HTML5、CSS3和JavaScript构建响应式界面,确保在PC和移动设备上都能提供良好的用户体验。我们特别注重页面加载速度的优化,因为电商平台的转化率与页面加载时间密切相关。
-
业务逻辑层:这是系统的核心,处理所有业务规则和流程。我们将其进一步细分为:
- 商品服务(Product Service)
- 订单服务(Order Service)
- 用户服务(User Service)
- 支付服务(Payment Service)
-
数据访问层:使用ThinkPHP的ORM(对象关系映射)功能与MySQL数据库交互。为了提高性能,我们实现了多级缓存策略:
- 使用Redis缓存热门商品数据
- 使用Memcached缓存会话数据
- 使用文件缓存静态页面片段
数据库设计遵循第三范式,但针对高频查询做了适当的反范式化优化。例如,在订单表中冗余了商品名称和图片信息,避免在显示订单列表时频繁联表查询。
3. 核心功能模块实现
3.1 商品模块
商品模块是电商系统的核心,我们实现了以下关键功能:
- 商品分类体系:采用无限级分类设计,支持数码产品的多层级分类(如:手机→智能手机→品牌→型号)。
php复制// 商品分类模型
class CategoryModel extends Model {
// 获取分类树
public function getCategoryTree($parent_id = 0) {
$list = $this->where(['parent_id'=>$parent_id])->select();
foreach($list as &$item){
$item['children'] = $this->getCategoryTree($item['id']);
}
return $list;
}
// 获取分类面包屑导航
public function getBreadcrumb($cat_id) {
$breadcrumb = [];
while($cat_id > 0) {
$cat = $this->find($cat_id);
array_unshift($breadcrumb, $cat);
$cat_id = $cat['parent_id'];
}
return $breadcrumb;
}
}
- 商品多规格支持:针对数码产品常见的多规格需求(如手机的颜色、存储容量),我们设计了灵活的规格系统:
php复制// 商品规格关联表设计
CREATE TABLE `product_spec` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) NOT NULL COMMENT '商品ID',
`spec_name` varchar(50) NOT NULL COMMENT '规格名称(如颜色)',
`spec_value` varchar(50) NOT NULL COMMENT '规格值(如红色)',
`price` decimal(10,2) NOT NULL COMMENT '规格价格',
`stock` int(11) NOT NULL COMMENT '库存',
`image` varchar(255) DEFAULT NULL COMMENT '规格图片',
PRIMARY KEY (`id`),
KEY `product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 商品搜索与筛选:实现了基于Elasticsearch的商品全文搜索,以及多条件筛选功能:
php复制// 商品搜索控制器
class SearchController extends Controller {
public function index() {
$keyword = I('get.keyword', '');
$category_id = I('get.category_id', 0);
$price_range = I('get.price_range', '');
$brand_id = I('get.brand_id', 0);
// 构建搜索条件
$where = ['status' => 1];
if(!empty($keyword)) {
$where['name'] = ['like', "%{$keyword}%"];
}
if($category_id > 0) {
$where['category_id'] = $category_id;
}
if(!empty($price_range)) {
list($min_price, $max_price) = explode('-', $price_range);
$where['price'] = ['between', [$min_price, $max_price]];
}
if($brand_id > 0) {
$where['brand_id'] = $brand_id;
}
// 分页查询
$count = M('Product')->where($where)->count();
$page = new \Think\Page($count, 20);
$list = M('Product')->where($where)
->limit($page->firstRow, $page->listRows)
->select();
$this->assign('list', $list);
$this->assign('page', $page->show());
$this->display();
}
}
3.2 购物车与订单模块
购物车和订单系统是电商平台的交易核心,我们特别注重数据一致性和安全性:
- 购物车设计:支持未登录用户使用Cookie存储购物车,登录后自动合并到数据库。
php复制// 购物车控制器
class CartController extends Controller {
// 添加商品到购物车
public function add() {
$product_id = I('post.product_id', 0);
$spec_id = I('post.spec_id', 0);
$quantity = I('post.quantity', 1);
// 验证商品和规格
$product = M('Product')->find($product_id);
if(empty($product) || $product['status'] != 1) {
$this->error('商品不存在或已下架');
}
$spec = M('ProductSpec')->find($spec_id);
if(empty($spec) || $spec['product_id'] != $product_id) {
$this->error('商品规格不存在');
}
// 检查库存
if($spec['stock'] < $quantity) {
$this->error('库存不足');
}
// 已登录用户
if(session('?user_id')) {
$cart = M('Cart')->where([
'user_id' => session('user_id'),
'product_id' => $product_id,
'spec_id' => $spec_id
])->find();
if($cart) {
// 更新数量
M('Cart')->where(['id'=>$cart['id']])->save([
'quantity' => $cart['quantity'] + $quantity,
'update_time' => time()
]);
} else {
// 新增记录
M('Cart')->add([
'user_id' => session('user_id'),
'product_id' => $product_id,
'spec_id' => $spec_id,
'quantity' => $quantity,
'create_time' => time(),
'update_time' => time()
]);
}
} else {
// 未登录用户使用Cookie
$cart = json_decode(cookie('cart'), true) ?: [];
$key = "{$product_id}_{$spec_id}";
if(isset($cart[$key])) {
$cart[$key]['quantity'] += $quantity;
} else {
$cart[$key] = [
'product_id' => $product_id,
'spec_id' => $spec_id,
'quantity' => $quantity
];
}
cookie('cart', json_encode($cart), 86400*30);
}
$this->success('添加购物车成功');
}
}
- 订单处理流程:采用状态机模式管理订单生命周期,确保订单状态转换的严谨性。
php复制// 订单状态定义
class OrderStatus {
const UNPAID = 0; // 待支付
const PAID = 1; // 已支付
const SHIPPED = 2; // 已发货
const COMPLETED = 3; // 已完成
const CANCELLED = 4; // 已取消
const REFUNDED = 5; // 已退款
// 状态转换规则
public static $transitions = [
self::UNPAID => [self::PAID, self::CANCELLED],
self::PAID => [self::SHIPPED, self::REFUNDED],
self::SHIPPED => [self::COMPLETED, self::REFUNDED],
];
// 检查状态转换是否合法
public static function canTransition($from, $to) {
return in_array($to, self::$transitions[$from] ?? []);
}
}
// 订单服务
class OrderService {
// 创建订单
public function createOrder($user_id, $cart_items, $address) {
// 开启事务
M()->startTrans();
try {
// 1. 验证购物车商品
$total_amount = 0;
$order_items = [];
foreach($cart_items as $item) {
$product = M('Product')->find($item['product_id']);
$spec = M('ProductSpec')->find($item['spec_id']);
// 验证商品状态和库存
if(empty($product) || $product['status'] != 1 ||
empty($spec) || $spec['stock'] < $item['quantity']) {
throw new \Exception("商品{$product['name']}库存不足或已下架");
}
// 计算总价
$subtotal = $spec['price'] * $item['quantity'];
$total_amount += $subtotal;
// 记录订单商品
$order_items[] = [
'product_id' => $product['id'],
'product_name' => $product['name'],
'spec_id' => $spec['id'],
'spec_name' => $spec['spec_name'],
'spec_value' => $spec['spec_value'],
'price' => $spec['price'],
'quantity' => $item['quantity'],
'subtotal' => $subtotal,
'image' => $spec['image'] ?: $product['image']
];
}
// 2. 创建订单
$order_data = [
'order_no' => $this->generateOrderNo(),
'user_id' => $user_id,
'total_amount' => $total_amount,
'status' => OrderStatus::UNPAID,
'create_time' => time(),
'update_time' => time(),
'address' => json_encode($address)
];
$order_id = M('Order')->add($order_data);
if(!$order_id) {
throw new \Exception('订单创建失败');
}
// 3. 保存订单商品
foreach($order_items as &$item) {
$item['order_id'] = $order_id;
}
if(!M('OrderItem')->addAll($order_items)) {
throw new \Exception('订单商品保存失败');
}
// 4. 扣减库存
foreach($cart_items as $item) {
if(M('ProductSpec')->where([
'id' => $item['spec_id'],
'stock' => ['egt', $item['quantity']]
])->setDec('stock', $item['quantity']) === false) {
throw new \Exception('库存扣减失败');
}
}
// 5. 清空购物车
M('Cart')->where(['user_id'=>$user_id])->delete();
// 提交事务
M()->commit();
return $order_id;
} catch(\Exception $e) {
// 回滚事务
M()->rollback();
throw $e;
}
}
// 生成订单号
private function generateOrderNo() {
return date('YmdHis') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
}
}
3.3 支付系统集成
支付是电商平台的关键环节,我们集成了主流的第三方支付接口:
- 支付网关设计:采用策略模式实现多支付方式支持,便于后续扩展新的支付渠道。
php复制// 支付接口抽象类
abstract class PaymentGateway {
abstract public function pay($order_no, $amount, $title, $notify_url);
abstract public function verify($data);
}
// 支付宝支付实现
class AlipayGateway extends PaymentGateway {
private $config;
public function __construct($config) {
$this->config = $config;
}
public function pay($order_no, $amount, $title, $notify_url) {
require_once VENDOR_PATH . 'alipay/AopSdk.php';
$aop = new \AopClient();
$aop->gatewayUrl = $this->config['gateway_url'];
$aop->appId = $this->config['app_id'];
$aop->rsaPrivateKey = $this->config['merchant_private_key'];
$aop->format = "json";
$aop->charset = "UTF-8";
$aop->signType = "RSA2";
$aop->alipayrsaPublicKey = $this->config['alipay_public_key'];
$request = new \AlipayTradePagePayRequest();
$request->setReturnUrl($this->config['return_url']);
$request->setNotifyUrl($notify_url);
$bizcontent = json_encode([
'out_trade_no' => $order_no,
'product_code' => 'FAST_INSTANT_TRADE_PAY',
'total_amount' => $amount,
'subject' => $title,
'body' => $title
]);
$request->setBizContent($bizcontent);
$result = $aop->pageExecute($request);
return $result;
}
public function verify($data) {
require_once VENDOR_PATH . 'alipay/AopSdk.php';
$aop = new \AopClient();
$aop->alipayrsaPublicKey = $this->config['alipay_public_key'];
return $aop->rsaCheckV1($data, NULL, "RSA2");
}
}
// 支付工厂类
class PaymentFactory {
public static function create($gateway, $config) {
switch(strtolower($gateway)) {
case 'alipay':
return new AlipayGateway($config['alipay']);
case 'wechat':
return new WechatGateway($config['wechat']);
default:
throw new \Exception('不支持的支付方式');
}
}
}
// 支付控制器
class PaymentController extends Controller {
public function pay($order_id) {
$order = M('Order')->find($order_id);
if(empty($order) || $order['user_id'] != session('user_id')) {
$this->error('订单不存在');
}
if($order['status'] != OrderStatus::UNPAID) {
$this->error('订单状态异常');
}
$gateway = I('post.gateway', 'alipay');
$payment = PaymentFactory::create($gateway, C('PAYMENT'));
$notify_url = U('Payment/notify', ['gateway'=>$gateway], true, true);
$html = $payment->pay($order['order_no'], $order['total_amount'], '数码商城订单', $notify_url);
echo $html;
}
public function notify($gateway) {
$payment = PaymentFactory::create($gateway, C('PAYMENT'));
$data = $_POST;
if($payment->verify($data)) {
$order_no = $data['out_trade_no'];
$order = M('Order')->where(['order_no'=>$order_no])->find();
if($order && $order['status'] == OrderStatus::UNPAID) {
// 更新订单状态
M('Order')->where(['id'=>$order['id']])->save([
'status' => OrderStatus::PAID,
'pay_time' => time(),
'update_time' => time()
]);
// 记录支付日志
M('PaymentLog')->add([
'order_id' => $order['id'],
'gateway' => $gateway,
'amount' => $order['total_amount'],
'transaction_id' => $data['trade_no'],
'data' => json_encode($data),
'create_time' => time()
]);
echo 'success';
return;
}
}
echo 'fail';
}
}
4. 后台管理系统实现
4.1 基于RBAC的权限控制
后台管理系统采用了基于角色的访问控制(RBAC)模型,确保不同职责的管理员只能访问其权限范围内的功能:
php复制// 权限验证行为
class AuthCheckBehavior extends \Think\Behavior {
public function run(&$params) {
// 排除公开访问的控制器和方法
$public_controllers = ['Login', 'Public'];
$controller = CONTROLLER_NAME;
$action = ACTION_NAME;
if(in_array($controller, $public_controllers)) {
return;
}
// 检查登录状态
if(!session('?admin_id')) {
redirect(U('Login/index'));
}
// 超级管理员拥有所有权限
if(session('admin_id') == 1) {
return;
}
// 检查权限
$node = strtolower($controller . '/' . $action);
$auth = new \Think\Auth();
if(!$auth->check($node, session('admin_id'))) {
if(IS_AJAX) {
$this->ajaxReturn(['status'=>0, 'msg'=>'无权限操作']);
} else {
$this->error('无权限操作');
}
}
}
}
// 权限验证类
class Auth {
// 检查权限
public function check($rule, $uid) {
// 获取用户所有角色
$roles = M('AdminRole')->where(['admin_id'=>$uid])->getField('role_id', true);
if(empty($roles)) {
return false;
}
// 获取角色所有权限节点
$nodes = M('RoleNode')->where(['role_id'=>['in', $roles]])
->getField('node_id', true);
if(empty($nodes)) {
return false;
}
// 获取节点规则
$rule = strtolower($rule);
$where = [
'id' => ['in', $nodes],
'status' => 1,
'rule' => $rule
];
return M('Node')->where($where)->find() ? true : false;
}
}
4.2 商品管理功能
后台商品管理模块提供了完整的商品CRUD操作,以及批量导入导出功能:
php复制// 商品控制器
class ProductController extends AdminController {
// 商品列表
public function index() {
$category_id = I('get.category_id', 0);
$keyword = I('get.keyword', '');
$status = I('get.status', -1, 'intval');
$where = [];
if($category_id > 0) {
$where['category_id'] = $category_id;
}
if(!empty($keyword)) {
$where['name'] = ['like', "%{$keyword}%"];
}
if($status != -1) {
$where['status'] = $status;
}
$count = M('Product')->where($where)->count();
$page = new \Think\Page($count, 20);
$list = M('Product')->where($where)
->order('sort DESC, id DESC')
->limit($page->firstRow, $page->listRows)
->select();
// 获取分类树
$category_tree = D('Category')->getCategoryTree();
$this->assign('list', $list);
$this->assign('page', $page->show());
$this->assign('category_tree', $category_tree);
$this->display();
}
// 添加/编辑商品
public function edit($id = 0) {
if(IS_POST) {
$data = I('post.');
// 验证数据
if(empty($data['name'])) {
$this->error('商品名称不能为空');
}
if(empty($data['category_id'])) {
$this->error('请选择商品分类');
}
// 处理主图上传
if(!empty($_FILES['image']['name'])) {
$upload = new \Think\Upload();
$upload->maxSize = 2 * 1024 * 1024; // 2MB
$upload->exts = ['jpg', 'gif', 'png', 'jpeg'];
$upload->rootPath = './Uploads/';
$upload->savePath = 'product/';
$info = $upload->uploadOne($_FILES['image']);
if($info) {
$data['image'] = $info['savepath'] . $info['savename'];
} else {
$this->error($upload->getError());
}
} elseif($id > 0 && empty($data['image'])) {
unset($data['image']);
}
// 处理相册图片
if(!empty($_FILES['album']['name'][0])) {
$upload = new \Think\Upload();
$upload->maxSize = 2 * 1024 * 1024; // 2MB
$upload->exts = ['jpg', 'gif', 'png', 'jpeg'];
$upload->rootPath = './Uploads/';
$upload->savePath = 'product/album/';
$info = $upload->upload();
if($info) {
$album = [];
foreach($info as $file) {
$album[] = $file['savepath'] . $file['savename'];
}
$data['album'] = implode(',', $album);
} else {
$this->error($upload->getError());
}
}
// 保存数据
if($id > 0) {
// 更新
$result = M('Product')->where(['id'=>$id])->save($data);
} else {
// 新增
$data['create_time'] = time();
$result = M('Product')->add($data);
}
if($result !== false) {
$this->success('操作成功', U('index'));
} else {
$this->error('操作失败');
}
} else {
// 获取分类树
$category_tree = D('Category')->getCategoryTree();
if($id > 0) {
$info = M('Product')->find($id);
$this->assign('info', $info);
}
$this->assign('category_tree', $category_tree);
$this->display();
}
}
}
5. 性能优化与安全实践
5.1 性能优化策略
-
数据库优化:
- 合理设计索引,特别是高频查询字段
- 使用EXPLAIN分析慢查询
- 对大表进行分表处理(如订单表按时间分表)
-
缓存策略:
- 使用Redis缓存热门商品数据
- 实现页面静态化和片段缓存
- 使用OPcache加速PHP执行
php复制// Redis缓存示例
class Cache {
private static $redis;
public static function init() {
if(!self::$redis) {
self::$redis = new \Redis();
self::$redis->connect('127.0.0.1', 6379);
}
return self::$redis;
}
// 获取热门商品
public static function getHotProducts($limit = 10) {
$redis = self::init();
$cache_key = 'hot_products';
if($redis->exists($cache_key)) {
return json_decode($redis->get($cache_key), true);
} else {
$products = M('Product')
->where(['status'=>1, 'is_hot'=>1])
->order('sales DESC')
->limit($limit)
->select();
$redis->setex($cache_key, 3600, json_encode($products));
return $products;
}
}
}
- 前端优化:
- 使用CDN加速静态资源加载
- 实现图片懒加载
- 合并和压缩CSS/JS文件
5.2 安全实践
- 输入验证与过滤:
- 对所有用户输入进行过滤
- 使用预处理语句防止SQL注入
- 对输出内容进行HTML转义防止XSS攻击
php复制// 安全过滤示例
class Security {
// 过滤XSS
public static function cleanXSS($data) {
if(is_array($data)) {
foreach($data as $key => $val) {
$data[$key] = self::cleanXSS($val);
}
} else {
$data = htmlspecialchars($data, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
return $data;
}
// 过滤SQL注入
public static function cleanSQL($data) {
if(is_array($data)) {
foreach($data as $key => $val) {
$data[$key] = self::cleanSQL($val);
}
} else {
$data = addslashes($data);
}
return $data;
}
}
// 在控制器中使用
$input = I('post.');
$input = Security::cleanXSS($input);
$input = Security::cleanSQL($input);
- CSRF防护:
- 为所有表单添加CSRF令牌
- 验证请求来源
php复制// CSRF防护中间件
class CsrfMiddleware {
public static function check() {
if(IS_POST) {
$token = I('post._csrf_token');
if(!$token || $token != session('_csrf_token')) {
die('CSRF token验证失败');
}
}
}
public static function generateToken() {
$token = md5(uniqid(mt_rand(), true));
session('_csrf_token', $token);
return $token;
}
}
// 在模板中使用
<input type="hidden" name="_csrf_token" value="<?php echo CsrfMiddleware::generateToken(); ?>">
- 支付安全:
- 验证支付回调的签名
- 记录完整的支付日志
- 实现订单金额的二次验证
6. 部署与运维
6.1 环境部署
我们推荐使用以下环境部署方案:
-
开发环境:
- PHPStudy/WampServer集成环境
- PHP 7.4+
- MySQL 5.7+
- Apache/Nginx
-
生产环境:
- Linux服务器(CentOS/Ubuntu)
- Nginx + PHP-FPM
- MySQL主从复制
- Redis缓存服务器
提示:生产环境建议使用Docker容器化部署,便于管理和扩展。以下是一个简单的Docker Compose配置示例:
yaml复制version: '3'
services:
web:
image: nginx:1.19
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./code:/var/www/html
- ./logs:/var/log/nginx
depends_on:
- php
php:
image: php:7.4-fpm
volumes:
- ./code:/var/www/html
- ./php.ini:/usr/local/etc/php/php.ini
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: yourpassword
MYSQL_DATABASE: shop
MYSQL_USER: shop
MYSQL_PASSWORD: shoppass
volumes:
- ./mysql:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6.0
ports:
- "6379:6379"
volumes:
- ./redis:/data
6.2 监控与日志
-
系统监控:
- 使用Prometheus + Grafana监控服务器资源
- 设置关键业务指标告警
-
日志收集:
- 使用ELK(Elasticsearch+Logstash+Kibana)收集和分析日志
- 记录关键业务操作日志
php复制// 日志记录示例
class Log {
const LEVEL_ERROR = 'ERROR';
const LEVEL_WARNING = 'WARNING';
const LEVEL_INFO = 'INFO';
const LEVEL_DEBUG = 'DEBUG';
public static function write($message, $level = self::LEVEL_INFO, $context = []) {
$log_dir = RUNTIME_PATH . 'Logs/';
if(!is_dir($log_dir)) {
mkdir($log_dir, 0755, true);
}
$log_file = $log_dir . date('Y-m-d') . '.log';
$log_content = sprintf(
"[%s] %s: %s %s\n",
date('Y-m-d H:i:s'),
$level,
$message,
json_encode($context, JSON_UNESCAPED_UNICODE)
);
file_put_contents($log_file, $log_content, FILE_APPEND);
}
}
// 使用示例
try {
// 业务代码
} catch(Exception $e) {
Log::write('订单创建失败', Log::LEVEL_ERROR, [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'data' => $_POST
]);
}
7. 常见问题与解决方案
在实际开发和运维过程中,我们遇到了许多典型问题,以下是部分问题的解决方案:
- 高并发下的库存超卖问题
解决方案:使用Redis原子操作实现分布式锁
php复制// 使用Redis实现分布式锁
class RedisLock {
private $redis;
private $lockKey;
private $timeout;
public function __construct($lockKey, $timeout = 10) {
$this->redis = Cache::init();
$this->lockKey = 'lock:' . $lockKey;
$this->timeout = $timeout;
}
public function acquire() {
$start = time();
while(true) {
// 尝试获取锁
$result = $this->redis->setnx($this->lockKey, time() + $this->timeout);
if($result) {
// 获取锁成功
return true;
}
// 检查锁是否过期
$lockTime = $this->redis->get($this->lockKey);
if($lockTime && $lockTime < time()) {
// 锁已过期,尝试获取
$newLockTime = time() + $this->timeout;
$currentLockTime = $this->redis->getSet($this->lockKey, $newLockTime);
if($currentLockTime == $lockTime) {
return true;
}
}
// 等待一段时间再重试
if(time() - $start > $this->timeout) {
return false;
}
usleep(100000); // 100ms
}
}
public function release() {
$this->redis->del($this->lockKey);
}
}
// 在库存扣减中使用
$lock = new RedisLock('product_' . $product_id);
if($lock->acquire()) {
try {
// 扣减库存
$spec = M('ProductSpec')->find($spec_id);
if($spec['stock'] >= $quantity) {
M('ProductSpec')->where(['id'=>$spec_id])->setDec('stock', $quantity);
} else {
throw new Exception('库存不足');
}
} finally {
$lock->release();
}
} else {
throw new Exception('系统繁忙,请稍后再试');
}
- 支付回调处理幂等性问题
解决方案:使用唯一事务ID和状态机确保幂等性
php复制// 支付回调处理
public function notify($gateway) {
$payment = PaymentFactory::create($gateway, C('PAYMENT'));
$data = $_POST;
if($payment->verify($data)) {
$transaction_id = $data['trade_no']; // 第三方支付交易号
$order_no = $data['out_trade_no']; // 商户订单号
// 检查是否已处理过
$log = M('PaymentLog')->where(['transaction_id'=>$transaction_id])->find();
if($log && $log['status'] == 1) {
echo 'success'; // 已处理过,直接返回成功
return;
}
// 开启事务
M()->startTrans();
try {
// 查询订单
$order = M('Order')->where(['order_no'=>$order_no])->lock(true)->find();
if(!$order) {
throw new Exception('订单不存在');
}
// 检查订单状态
if($order['status'] != OrderStatus::UNPAID) {
throw new Exception('订单状态异常');
}
// 检查金额是否匹配
if($order['total_amount'] != $data['total_amount']) {
throw new Exception('金额不匹配');
}
// 更新订单状态
$result = M('Order')->where