发布日期:2023年11月
技术栈:ThinkPHP 6.0 + MySQL + Redis + JWT
项目概述:现代化电商后台API架构设计
本教程将带领大家使用ThinkPHP 6.0构建一个完整的电商后台API系统,涵盖用户认证、商品管理、订单处理、支付回调等核心功能。我们将采用分层架构设计,实现高内聚低耦合的代码结构。
技术架构图
├── app
│ ├── controller # 控制器层
│ ├── service # 业务逻辑层
│ ├── repository # 数据访问层
│ ├── middleware # 中间件
│ ├── validate # 验证器
│ └── exception # 异常处理
├── config
├── route
└── extend # 扩展类库
第一部分:项目初始化与基础配置
1.1 环境准备与项目创建
# 使用Composer创建ThinkPHP 6.0项目
composer create-project topthink/think tp6-ecommerce-api
# 安装扩展包
composer require topthink/think-multi-app
composer require firebase/php-jwt
composer require topthink/think-captcha
# 配置数据库连接
# config/database.php
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
'type' => 'mysql',
'hostname' => '127.0.0.1',
'database' => 'ecommerce',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
'prefix' => 'ec_',
'break_reconnect' => true, # 断线重连
'trigger_sql' => env('APP_DEBUG', false),
]
]
];
1.2 多应用模式配置
# 启用多应用模式
# config/app.php
return [
'auto_multi_app' => true,
'app_map' => [
'admin' => 'admin', # 后台管理
'api' => 'api', # 用户接口
'open' => 'openapi', # 开放平台
],
'domain_bind' => [
'admin.example.com' => 'admin',
'api.example.com' => 'api',
'open.example.com' => 'openapi',
]
];
# 创建应用目录结构
mkdir -p app/admin/controller
mkdir -p app/api/{controller,service,repository,validate}
mkdir -p app/openapi/controller
第二部分:核心模块开发实战
2.1 JWT身份认证系统
// app/api/service/AuthService.php
namespace appapiservice;
use thinkfacadeCache;
use FirebaseJWTJWT;
use FirebaseJWTKey;
class AuthService
{
private static $secretKey = 'your-secret-key-here';
private static $algorithm = 'HS256';
/**
* 生成JWT令牌
*/
public static function generateToken($userId, $userData = [])
{
$payload = [
'iss' => 'ecommerce-api', # 签发者
'aud' => 'user', # 接收方
'iat' => time(), # 签发时间
'exp' => time() + 7200, # 2小时过期
'uid' => $userId,
'data' => $userData
];
$token = JWT::encode($payload, self::$secretKey, self::$algorithm);
# 存储到Redis,实现令牌管理
Cache::store('redis')->set('user_token:' . $userId, $token, 7200);
return $token;
}
/**
* 验证JWT令牌
*/
public static function verifyToken($token)
{
try {
$decoded = JWT::decode($token, new Key(self::$secretKey, self::$algorithm));
# 验证Redis中的令牌是否一致
$redisToken = Cache::store('redis')->get('user_token:' . $decoded->uid);
if ($redisToken !== $token) {
throw new Exception('令牌已失效');
}
return (array)$decoded;
} catch (Exception $e) {
throw new appapiexceptionAuthException('身份验证失败: ' . $e->getMessage());
}
}
}
2.2 商品服务层设计
// app/api/service/ProductService.php
namespace appapiservice;
use appapirepositoryProductRepository;
use thinkfacadeCache;
class ProductService
{
protected $productRepo;
public function __construct(ProductRepository $productRepo)
{
$this->productRepo = $productRepo;
}
/**
* 获取商品详情(带缓存)
*/
public function getProductDetail($productId, $withSku = true)
{
$cacheKey = 'product_detail:' . $productId;
# 尝试从缓存获取
$product = Cache::get($cacheKey);
if (!$product) {
$product = $this->productRepo->getProductWithRelations($productId);
if ($product && $withSku) {
$product['skus'] = $this->productRepo->getProductSkus($productId);
}
# 缓存30分钟
Cache::set($cacheKey, $product, 1800);
}
return $product;
}
/**
* 商品搜索(支持ES或数据库搜索)
*/
public function searchProducts($params, $page = 1, $pageSize = 20)
{
$searchParams = $this->buildSearchParams($params);
# 如果有Elasticsearch,优先使用
if (class_exists('ElasticsearchClient') && config('elasticsearch.enable')) {
return $this->searchByElasticsearch($searchParams, $page, $pageSize);
}
# 降级到数据库搜索
return $this->productRepo->searchByDatabase($searchParams, $page, $pageSize);
}
/**
* 扣减库存(使用Redis原子操作)
*/
public function decreaseStock($productId, $skuId, $quantity)
{
$lockKey = 'stock_lock:' . $productId . ':' . $skuId;
$stockKey = 'product_stock:' . $skuId;
# 使用Redis分布式锁
$lock = Cache::store('redis')->lock($lockKey, 3);
try {
if ($lock->acquire()) {
$currentStock = Cache::get($stockKey);
if ($currentStock === null) {
# 从数据库加载库存
$currentStock = $this->productRepo->getSkuStock($skuId);
Cache::set($stockKey, $currentStock, 3600);
}
if ($currentStock asyncUpdateDatabaseStock($skuId, $newStock);
return true;
}
} finally {
$lock->release();
}
return false;
}
}
2.3 订单业务处理
// app/api/service/OrderService.php
namespace appapiservice;
use thinkfacadeDb;
use appapiexceptionBusinessException;
class OrderService
{
/**
* 创建订单(事务处理)
*/
public function createOrder($userId, $items, $addressId)
{
Db::startTrans();
try {
# 1. 验证商品和库存
$verifiedItems = $this->verifyItems($items);
# 2. 计算订单金额
$orderAmount = $this->calculateOrderAmount($verifiedItems);
# 3. 生成订单号(分布式唯一ID)
$orderSn = $this->generateOrderSn();
# 4. 创建订单主记录
$orderData = [
'order_sn' => $orderSn,
'user_id' => $userId,
'total_amount' => $orderAmount,
'pay_amount' => $orderAmount,
'address_id' => $addressId,
'status' => 0, # 待支付
'create_time' => time()
];
$orderId = Db::name('order')->insertGetId($orderData);
# 5. 创建订单商品明细
$orderItems = [];
foreach ($verifiedItems as $item) {
$orderItems[] = [
'order_id' => $orderId,
'product_id' => $item['product_id'],
'sku_id' => $item['sku_id'],
'quantity' => $item['quantity'],
'price' => $item['price'],
'total_price' => $item['quantity'] * $item['price']
];
# 扣减库存
$this->decreaseStock($item['product_id'], $item['sku_id'], $item['quantity']);
}
Db::name('order_item')->insertAll($orderItems);
# 6. 记录订单日志
$this->logOrderAction($orderId, 'create', '订单创建成功');
Db::commit();
# 7. 发送订单创建通知
$this->sendOrderNotification($userId, $orderSn);
return $orderId;
} catch (Exception $e) {
Db::rollback();
throw new BusinessException('订单创建失败: ' . $e->getMessage());
}
}
/**
* 生成分布式唯一订单号
*/
private function generateOrderSn()
{
$prefix = date('YmdHis');
$micro = substr(microtime(), 2, 6);
$random = mt_rand(1000, 9999);
return $prefix . $micro . $random;
}
}
第三部分:高级特性与优化
3.1 自定义验证器与中间件
// app/api/validate/OrderValidate.php
namespace appapivalidate;
use thinkValidate;
class OrderValidate extends Validate
{
protected $rule = [
'items' => 'require|array|checkItems',
'address_id' => 'require|integer|gt:0',
'coupon_id' => 'integer',
'remark' => 'max:200'
];
protected $message = [
'items.require' => '商品不能为空',
'items.array' => '商品格式错误',
'address_id.require' => '收货地址不能为空'
];
/**
* 自定义验证规则:验证商品项
*/
protected function checkItems($value)
{
if (!is_array($value) || empty($value)) {
return '商品列表不能为空';
}
foreach ($value as $item) {
if (!isset($item['product_id']) || !isset($item['sku_id']) || !isset($item['quantity'])) {
return '商品信息不完整';
}
if ($item['quantity'] header('Authorization');
if (!$token) {
return json(['code' => 401, 'msg' => '缺少身份令牌']);
}
# 移除Bearer前缀
if (strpos($token, 'Bearer ') === 0) {
$token = substr($token, 7);
}
try {
$userInfo = AuthService::verifyToken($token);
$request->user = $userInfo;
# 记录操作日志
$this->logOperation($request, $userInfo);
} catch (Exception $e) {
return json(['code' => 401, 'msg' => $e->getMessage()]);
}
return $next($request);
}
private function logOperation($request, $userInfo)
{
$logData = [
'user_id' => $userInfo['uid'],
'path' => $request->pathinfo(),
'method' => $request->method(),
'ip' => $request->ip(),
'params' => json_encode($request->param(), JSON_UNESCAPED_UNICODE),
'create_time' => time()
];
# 异步写入日志
thinkfacadeQueue::push('appapijobOperationLog', $logData);
}
}
3.2 队列与异步任务处理
// app/api/job/OrderTimeoutJob.php
namespace appapijob;
use thinkqueueJob;
use thinkfacadeDb;
class OrderTimeoutJob
{
/**
* 订单超时未支付处理
*/
public function fire(Job $job, $data)
{
try {
$orderId = $data['order_id'];
# 检查订单状态
$order = Db::name('order')
->where('id', $orderId)
->where('status', 0) # 待支付
->find();
if (!$order) {
$job->delete();
return;
}
# 判断是否超时(30分钟)
if (time() - $order['create_time'] > 1800) {
# 更新订单状态为已取消
Db::name('order')
->where('id', $orderId)
->update([
'status' => 3, # 已取消
'cancel_time' => time(),
'cancel_reason' => '超时未支付'
]);
# 恢复库存
$this->restoreStock($orderId);
# 发送取消通知
$this->sendCancelNotification($order['user_id'], $order['order_sn']);
}
$job->delete();
} catch (Exception $e) {
# 记录错误日志
thinkfacadeLog::error('订单超时处理失败: ' . $e->getMessage());
# 失败重试
if ($job->attempts() release(60); # 1分钟后重试
}
}
}
/**
* 恢复库存
*/
private function restoreStock($orderId)
{
$items = Db::name('order_item')->where('order_id', $orderId)->select();
foreach ($items as $item) {
# 异步恢复库存
thinkfacadeQueue::push('appapijobRestoreStockJob', [
'product_id' => $item['product_id'],
'sku_id' => $item['sku_id'],
'quantity' => $item['quantity']
]);
}
}
}
// 在控制器中使用队列
class OrderController
{
public function create()
{
// ... 创建订单逻辑
# 延迟30分钟执行订单超时检查
$delay = 1800; # 30分钟
thinkfacadeQueue::later($delay, 'appapijobOrderTimeoutJob', [
'order_id' => $orderId
]);
}
}
第四部分:API文档与测试
4.1 使用注解生成API文档
// app/api/controller/v1/ProductController.php
namespace appapicontrollerv1;
use thinkannotationRoute;
use thinkannotationrouteGroup;
use thinkannotationrouteMiddleware;
/**
* @Group("api/v1/product")
* @Middleware("appapimiddlewareAuthMiddleware")
*/
class ProductController
{
/**
* 获取商品列表
* @Route("list", method="GET")
* @param int $page 页码
* @param int $size 每页数量
* @param string $keyword 搜索关键词
* @return thinkResponse
*/
public function list($page = 1, $size = 20, $keyword = '')
{
$service = new appapiserviceProductService();
$result = $service->searchProducts([
'keyword' => $keyword,
'page' => $page,
'size' => $size
]);
return json([
'code' => 200,
'msg' => 'success',
'data' => $result
]);
}
/**
* 获取商品详情
* @Route(":id/detail", method="GET")
* @param int $id 商品ID
* @return thinkResponse
*/
public function detail($id)
{
$service = new appapiserviceProductService();
$product = $service->getProductDetail($id);
if (!$product) {
return json(['code' => 404, 'msg' => '商品不存在']);
}
return json([
'code' => 200,
'msg' => 'success',
'data' => $product
]);
}
}
4.2 单元测试示例
// tests/api/ProductTest.php
namespace testsapi;
use PHPUnitFrameworkTestCase;
use thinkfacadeDb;
class ProductTest extends TestCase
{
protected function setUp(): void
{
# 初始化应用
$app = new thinkApp();
$app->initialize();
# 清空测试数据
Db::name('product')->where('id', '>', 0)->delete();
}
/**
* 测试商品创建
*/
public function testCreateProduct()
{
$data = [
'name' => '测试商品',
'price' => 99.99,
'stock' => 100,
'category_id' => 1,
'status' => 1
];
$id = Db::name('product')->insertGetId($data);
$this->assertGreaterThan(0, $id);
$product = Db::name('product')->find($id);
$this->assertEquals('测试商品', $product['name']);
$this->assertEquals(99.99, $product['price']);
}
/**
* 测试库存扣减
*/
public function testDecreaseStock()
{
# 创建测试商品
$productId = Db::name('product')->insertGetId([
'name' => '库存测试商品',
'stock' => 50
]);
$service = new appapiserviceProductService();
# 扣减库存
$result = $service->decreaseStock($productId, 0, 10);
$this->assertTrue($result);
# 验证库存
$product = Db::name('product')->find($productId);
$this->assertEquals(40, $product['stock']);
}
}
第五部分:部署与监控
5.1 Nginx配置优化
# /etc/nginx/conf.d/ecommerce.conf
server {
listen 80;
server_name api.example.com;
root /var/www/ecommerce-api/public;
index index.php;
# 静态文件缓存
location ~* .(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# PHP处理
location ~ .php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# 优化参数
fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
}
# API限流
location ~ ^/api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
try_files $uri $uri/ /index.php?$query_string;
}
# 健康检查
location /health {
access_log off;
return 200 "healthyn";
}
}
5.2 性能监控配置
// config/monitor.php
return [
# 慢查询日志
'slow_query' => [
'enable' => true,
'threshold' => 1000, # 1秒
'log_path' => runtime_path('log/slow_query.log')
],
# 接口性能监控
'api_monitor' => [
'enable' => true,
'exclude' => ['/health', '/metrics'],
'storage' => 'redis', # 存储到Redis
'ttl' => 86400 # 保留24小时
],
# 错误监控
'error_monitor' => [
'enable' => true,
'notify' => [
'email' => 'devops@example.com',
'dingtalk' => 'https://oapi.dingtalk.com/robot/send'
],
'levels' => ['error', 'critical']
]
];
// 中间件:接口性能监控
class MonitorMiddleware
{
public function handle($request, Closure $next)
{
$startTime = microtime(true);
$response = $next($request);
$endTime = microtime(true);
$executionTime = ($endTime - $startTime) * 1000; # 毫秒
# 记录慢请求
if ($executionTime > 1000) {
thinkfacadeLog::record([
'path' => $request->pathinfo(),
'method' => $request->method(),
'time' => $executionTime,
'params' => $request->param()
], 'slow');
}
# 添加响应头
$response->header([
'X-Response-Time' => $executionTime . 'ms',
'X-API-Version' => '1.0'
]);
return $response;
}
}
总结与最佳实践
架构设计要点
- 分层架构:Controller → Service → Repository 明确职责分离
- 依赖注入:使用容器管理依赖,提高可测试性
- 缓存策略:Redis多级缓存,提升系统性能
- 队列解耦:耗时操作异步处理,提高响应速度
- 监控告警:完善的监控体系,快速定位问题
性能优化建议
- 数据库连接使用连接池,避免频繁创建连接
- 合理使用索引,定期分析慢查询
- 静态资源使用CDN加速
- API响应启用Gzip压缩
- 使用OPcache提升PHP执行效率
安全防护措施
- 输入参数严格验证和过滤
- SQL查询使用参数绑定
- 敏感数据加密存储
- API接口限流防刷
- 定期更新依赖包版本
通过本教程,我们构建了一个完整的电商后台API系统,涵盖了ThinkPHP 6.0的核心特性和企业级开发的最佳实践。这个项目架构可以扩展到更复杂的业务场景,为你的下一个商业项目提供坚实的基础。
// 代码高亮和复制功能
document.querySelectorAll(‘pre code’).forEach((block) => {
// 添加复制按钮
const copyBtn = document.createElement(‘button’);
copyBtn.textContent = ‘复制’;
copyBtn.style.cssText = ‘position:absolute;right:10px;top:10px;padding:2px 8px;background:#f0f0f0;border:1px solid #ddd;border-radius:3px;cursor:pointer;font-size:12px;’;
copyBtn.onclick = function() {
const text = block.textContent;
navigator.clipboard.writeText(text).then(() => {
const original = this.textContent;
this.textContent = ‘已复制’;
setTimeout(() => {
this.textContent = original;
}, 1500);
});
};
const pre = block.parentNode;
pre.style.position = ‘relative’;
pre.style.paddingTop = ’30px’;
pre.appendChild(copyBtn);
// 添加语言标签
const langTag = document.createElement(‘span’);
langTag.textContent = ‘PHP’;
langTag.style.cssText = ‘position:absolute;left:10px;top:10px;background:#4CAF50;color:white;padding:2px 8px;border-radius:3px;font-size:12px;’;
pre.appendChild(langTag);
});
// 章节导航
const sections = document.querySelectorAll(‘section’);
const nav = document.createElement(‘div’);
nav.innerHTML = ‘
文章导航
- ‘ +
- ${title.textContent}
Array.from(sections).map(section => {
const title = section.querySelector(‘h2’);
return title ? `
` : ”;
}).join(”) + ‘
‘;
nav.style.cssText = ‘position:fixed;right:20px;top:100px;background:white;border:1px solid #ddd;padding:15px;border-radius:5px;max-width:250px;box-shadow:0 2px 10px rgba(0,0,0,0.1);’;
document.body.appendChild(nav);

