ThinkPHP6/8 项目实战案例与最佳实践总结

经过前面十四篇文章的深入学习,我们已经掌握了ThinkPHP6/8的核心技术。本文将通过一个完整的电商项目实战案例,总结ThinkPHP开发的最佳实践,帮助开发者构建高质量的企业级应用。

项目架构设计

整体架构概览

我们将构建一个现代化的电商系统,采用以下架构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
电商系统架构
├── 前端层 (Frontend)
│ ├── 用户端 (Customer App)
│ ├── 管理端 (Admin Panel)
│ └── 移动端 (Mobile App)
├── 网关层 (Gateway)
│ ├── API网关
│ ├── 负载均衡
│ └── 限流熔断
├── 应用层 (Application)
│ ├── 用户服务
│ ├── 商品服务
│ ├── 订单服务
│ ├── 支付服务
│ └── 营销服务
├── 基础设施层 (Infrastructure)
│ ├── 数据库 (MySQL)
│ ├── 缓存 (Redis)
│ ├── 消息队列 (RabbitMQ)
│ ├── 搜索引擎 (Elasticsearch)
│ └── 文件存储 (OSS)
└── 监控层 (Monitoring)
├── 日志收集
├── 性能监控
├── 错误追踪
└── 业务监控

目录结构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
project/
├── app/ # 应用目录
│ ├── common/ # 公共文件
│ │ ├── enum/ # 枚举类
│ │ ├── exception/ # 异常类
│ │ ├── helper/ # 辅助函数
│ │ └── trait/ # Trait类
│ ├── api/ # API模块
│ │ ├── controller/ # 控制器
│ │ ├── middleware/ # 中间件
│ │ ├── request/ # 请求验证
│ │ └── route/ # 路由定义
│ ├── admin/ # 管理后台模块
│ ├── service/ # 业务服务层
│ ├── repository/ # 数据仓库层
│ ├── model/ # 数据模型
│ ├── job/ # 队列任务
│ ├── command/ # 命令行工具
│ └── event/ # 事件处理
├── config/ # 配置文件
├── database/ # 数据库相关
│ ├── migrations/ # 数据库迁移
│ └── seeds/ # 数据填充
├── public/ # 公共资源
├── runtime/ # 运行时文件
├── vendor/ # 第三方包
├── tests/ # 测试文件
├── docs/ # 项目文档
└── scripts/ # 部署脚本

核心业务实现

用户服务实现

创建app/service/UserService.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
<?php
declare(strict_types=1);

namespace app\service;

use app\model\User;
use app\repository\UserRepository;
use app\common\exception\BusinessException;
use think\facade\Cache;
use think\facade\Event;

/**
* 用户服务类
* 处理用户相关的业务逻辑
*/
class UserService
{
/**
* 用户仓库
* @var UserRepository
*/
private $userRepository;

/**
* 构造函数
* @param UserRepository $userRepository 用户仓库
*/
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}

/**
* 用户注册
* @param array $data 注册数据
* @return User 用户对象
* @throws BusinessException
*/
public function register(array $data): User
{
// 验证用户是否已存在
if ($this->userRepository->existsByEmail($data['email'])) {
throw new BusinessException('邮箱已被注册');
}

if ($this->userRepository->existsByPhone($data['phone'])) {
throw new BusinessException('手机号已被注册');
}

// 密码加密
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);

// 生成用户唯一标识
$data['uuid'] = $this->generateUserUuid();

// 设置默认状态
$data['status'] = User::STATUS_ACTIVE;
$data['email_verified_at'] = null;
$data['phone_verified_at'] = null;

// 创建用户
$user = $this->userRepository->create($data);

// 触发用户注册事件
Event::trigger('user.registered', $user);

return $user;
}

/**
* 用户登录
* @param string $account 账号(邮箱或手机号)
* @param string $password 密码
* @return array 登录结果
* @throws BusinessException
*/
public function login(string $account, string $password): array
{
// 查找用户
$user = $this->userRepository->findByAccount($account);

if (!$user) {
throw new BusinessException('用户不存在');
}

// 检查用户状态
if ($user->status !== User::STATUS_ACTIVE) {
throw new BusinessException('用户已被禁用');
}

// 验证密码
if (!password_verify($password, $user->password)) {
// 记录登录失败
$this->recordLoginFailure($user->id);
throw new BusinessException('密码错误');
}

// 检查登录失败次数
$this->checkLoginFailures($user->id);

// 生成访问令牌
$token = $this->generateAccessToken($user);

// 更新登录信息
$this->updateLoginInfo($user);

// 清除登录失败记录
$this->clearLoginFailures($user->id);

// 触发登录事件
Event::trigger('user.logged_in', $user);

return [
'user' => $user->hidden(['password']),
'token' => $token,
'expires_in' => config('jwt.ttl', 3600),
];
}

/**
* 刷新访问令牌
* @param string $refreshToken 刷新令牌
* @return array 新的令牌信息
* @throws BusinessException
*/
public function refreshToken(string $refreshToken): array
{
// 验证刷新令牌
$payload = $this->validateRefreshToken($refreshToken);

// 获取用户信息
$user = $this->userRepository->find($payload['user_id']);

if (!$user || $user->status !== User::STATUS_ACTIVE) {
throw new BusinessException('用户状态异常');
}

// 生成新的访问令牌
$newToken = $this->generateAccessToken($user);

return [
'token' => $newToken,
'expires_in' => config('jwt.ttl', 3600),
];
}

/**
* 用户登出
* @param int $userId 用户ID
* @param string $token 访问令牌
*/
public function logout(int $userId, string $token): void
{
// 将令牌加入黑名单
$this->blacklistToken($token);

// 清除用户缓存
Cache::delete("user:info:{$userId}");

// 触发登出事件
Event::trigger('user.logged_out', $userId);
}

/**
* 获取用户信息
* @param int $userId 用户ID
* @return User|null 用户对象
*/
public function getUserInfo(int $userId): ?User
{
// 先从缓存获取
$cacheKey = "user:info:{$userId}";
$user = Cache::get($cacheKey);

if (!$user) {
$user = $this->userRepository->find($userId);
if ($user) {
// 缓存用户信息(1小时)
Cache::set($cacheKey, $user, 3600);
}
}

return $user ? $user->hidden(['password']) : null;
}

/**
* 更新用户信息
* @param int $userId 用户ID
* @param array $data 更新数据
* @return User 更新后的用户对象
* @throws BusinessException
*/
public function updateUserInfo(int $userId, array $data): User
{
$user = $this->userRepository->find($userId);

if (!$user) {
throw new BusinessException('用户不存在');
}

// 过滤不允许更新的字段
$allowedFields = ['nickname', 'avatar', 'gender', 'birthday', 'bio'];
$updateData = array_intersect_key($data, array_flip($allowedFields));

// 更新用户信息
$user = $this->userRepository->update($userId, $updateData);

// 清除缓存
Cache::delete("user:info:{$userId}");

// 触发用户信息更新事件
Event::trigger('user.info_updated', $user);

return $user->hidden(['password']);
}

/**
* 修改密码
* @param int $userId 用户ID
* @param string $oldPassword 旧密码
* @param string $newPassword 新密码
* @throws BusinessException
*/
public function changePassword(int $userId, string $oldPassword, string $newPassword): void
{
$user = $this->userRepository->find($userId);

if (!$user) {
throw new BusinessException('用户不存在');
}

// 验证旧密码
if (!password_verify($oldPassword, $user->password)) {
throw new BusinessException('原密码错误');
}

// 更新密码
$hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);
$this->userRepository->update($userId, ['password' => $hashedPassword]);

// 触发密码修改事件
Event::trigger('user.password_changed', $user);
}

/**
* 生成用户UUID
* @return string 用户UUID
*/
private function generateUserUuid(): string
{
return 'user_' . uniqid() . '_' . mt_rand(1000, 9999);
}

/**
* 生成访问令牌
* @param User $user 用户对象
* @return string 访问令牌
*/
private function generateAccessToken(User $user): string
{
$payload = [
'user_id' => $user->id,
'uuid' => $user->uuid,
'iat' => time(),
'exp' => time() + config('jwt.ttl', 3600),
];

return \Firebase\JWT\JWT::encode($payload, config('jwt.key'), 'HS256');
}

/**
* 验证刷新令牌
* @param string $token 刷新令牌
* @return array 令牌载荷
* @throws BusinessException
*/
private function validateRefreshToken(string $token): array
{
try {
$payload = \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key(config('jwt.refresh_key'), 'HS256'));
return (array) $payload;
} catch (\Exception $e) {
throw new BusinessException('刷新令牌无效');
}
}

/**
* 记录登录失败
* @param int $userId 用户ID
*/
private function recordLoginFailure(int $userId): void
{
$key = "login:failures:{$userId}";
$failures = Cache::get($key, 0);
Cache::set($key, $failures + 1, 1800); // 30分钟过期
}

/**
* 检查登录失败次数
* @param int $userId 用户ID
* @throws BusinessException
*/
private function checkLoginFailures(int $userId): void
{
$key = "login:failures:{$userId}";
$failures = Cache::get($key, 0);

if ($failures >= 5) {
throw new BusinessException('登录失败次数过多,请30分钟后重试');
}
}

/**
* 清除登录失败记录
* @param int $userId 用户ID
*/
private function clearLoginFailures(int $userId): void
{
Cache::delete("login:failures:{$userId}");
}

/**
* 更新登录信息
* @param User $user 用户对象
*/
private function updateLoginInfo(User $user): void
{
$this->userRepository->update($user->id, [
'last_login_at' => date('Y-m-d H:i:s'),
'last_login_ip' => request()->ip(),
]);
}

/**
* 将令牌加入黑名单
* @param string $token 访问令牌
*/
private function blacklistToken(string $token): void
{
$key = "token:blacklist:" . md5($token);
Cache::set($key, true, config('jwt.ttl', 3600));
}
}

商品服务实现

创建app/service/ProductService.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
<?php
declare(strict_types=1);

namespace app\service;

use app\model\Product;
use app\model\ProductCategory;
use app\model\ProductSku;
use app\repository\ProductRepository;
use app\common\exception\BusinessException;
use think\facade\Cache;
use think\facade\Event;
use think\facade\Db;

/**
* 商品服务类
* 处理商品相关的业务逻辑
*/
class ProductService
{
/**
* 商品仓库
* @var ProductRepository
*/
private $productRepository;

/**
* 构造函数
* @param ProductRepository $productRepository 商品仓库
*/
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}

/**
* 获取商品列表
* @param array $params 查询参数
* @return array 商品列表
*/
public function getProductList(array $params): array
{
$page = $params['page'] ?? 1;
$limit = $params['limit'] ?? 20;
$categoryId = $params['category_id'] ?? null;
$keyword = $params['keyword'] ?? null;
$sortBy = $params['sort_by'] ?? 'created_at';
$sortOrder = $params['sort_order'] ?? 'desc';

// 构建缓存键
$cacheKey = 'product:list:' . md5(serialize($params));

// 尝试从缓存获取
$result = Cache::get($cacheKey);

if (!$result) {
$query = $this->productRepository->query()
->where('status', Product::STATUS_ACTIVE)
->where('is_deleted', 0);

// 分类筛选
if ($categoryId) {
$query->where('category_id', $categoryId);
}

// 关键词搜索
if ($keyword) {
$query->where(function ($q) use ($keyword) {
$q->whereLike('name', "%{$keyword}%")
->whereOr('description', 'like', "%{$keyword}%")
->whereOr('tags', 'like', "%{$keyword}%");
});
}

// 排序
$query->order($sortBy, $sortOrder);

// 分页
$result = $query->paginate([
'list_rows' => $limit,
'page' => $page,
]);

// 缓存结果(5分钟)
Cache::set($cacheKey, $result, 300);
}

return $result->toArray();
}

/**
* 获取商品详情
* @param int $productId 商品ID
* @return array 商品详情
* @throws BusinessException
*/
public function getProductDetail(int $productId): array
{
$cacheKey = "product:detail:{$productId}";
$product = Cache::get($cacheKey);

if (!$product) {
$product = $this->productRepository->find($productId);

if (!$product || $product->is_deleted || $product->status !== Product::STATUS_ACTIVE) {
throw new BusinessException('商品不存在或已下架');
}

// 获取商品SKU
$product->skus = $this->getProductSkus($productId);

// 获取商品分类
$product->category = ProductCategory::find($product->category_id);

// 获取商品图片
$product->images = $this->getProductImages($productId);

// 获取商品属性
$product->attributes = $this->getProductAttributes($productId);

// 缓存商品详情(10分钟)
Cache::set($cacheKey, $product, 600);
}

// 增加浏览次数
$this->incrementViewCount($productId);

return $product->toArray();
}

/**
* 创建商品
* @param array $data 商品数据
* @return Product 商品对象
* @throws BusinessException
*/
public function createProduct(array $data): Product
{
// 开启事务
Db::startTrans();

try {
// 验证分类是否存在
$category = ProductCategory::find($data['category_id']);
if (!$category) {
throw new BusinessException('商品分类不存在');
}

// 生成商品编码
$data['code'] = $this->generateProductCode();

// 设置默认状态
$data['status'] = Product::STATUS_ACTIVE;
$data['is_deleted'] = 0;
$data['view_count'] = 0;
$data['sale_count'] = 0;

// 创建商品
$product = $this->productRepository->create($data);

// 创建商品SKU
if (isset($data['skus']) && !empty($data['skus'])) {
$this->createProductSkus($product->id, $data['skus']);
}

// 保存商品图片
if (isset($data['images']) && !empty($data['images'])) {
$this->saveProductImages($product->id, $data['images']);
}

// 保存商品属性
if (isset($data['attributes']) && !empty($data['attributes'])) {
$this->saveProductAttributes($product->id, $data['attributes']);
}

Db::commit();

// 触发商品创建事件
Event::trigger('product.created', $product);

return $product;
} catch (\Exception $e) {
Db::rollback();
throw new BusinessException('商品创建失败:' . $e->getMessage());
}
}

/**
* 更新商品
* @param int $productId 商品ID
* @param array $data 更新数据
* @return Product 更新后的商品对象
* @throws BusinessException
*/
public function updateProduct(int $productId, array $data): Product
{
$product = $this->productRepository->find($productId);

if (!$product) {
throw new BusinessException('商品不存在');
}

// 开启事务
Db::startTrans();

try {
// 更新商品基本信息
$product = $this->productRepository->update($productId, $data);

// 更新商品SKU
if (isset($data['skus'])) {
$this->updateProductSkus($productId, $data['skus']);
}

// 更新商品图片
if (isset($data['images'])) {
$this->updateProductImages($productId, $data['images']);
}

// 更新商品属性
if (isset($data['attributes'])) {
$this->updateProductAttributes($productId, $data['attributes']);
}

Db::commit();

// 清除缓存
$this->clearProductCache($productId);

// 触发商品更新事件
Event::trigger('product.updated', $product);

return $product;
} catch (\Exception $e) {
Db::rollback();
throw new BusinessException('商品更新失败:' . $e->getMessage());
}
}

/**
* 删除商品(软删除)
* @param int $productId 商品ID
* @throws BusinessException
*/
public function deleteProduct(int $productId): void
{
$product = $this->productRepository->find($productId);

if (!$product) {
throw new BusinessException('商品不存在');
}

// 软删除商品
$this->productRepository->update($productId, [
'is_deleted' => 1,
'deleted_at' => date('Y-m-d H:i:s'),
]);

// 清除缓存
$this->clearProductCache($productId);

// 触发商品删除事件
Event::trigger('product.deleted', $product);
}

/**
* 检查库存
* @param int $skuId SKU ID
* @param int $quantity 数量
* @return bool 是否有足够库存
*/
public function checkStock(int $skuId, int $quantity): bool
{
$sku = ProductSku::find($skuId);

if (!$sku) {
return false;
}

return $sku->stock >= $quantity;
}

/**
* 减少库存
* @param int $skuId SKU ID
* @param int $quantity 数量
* @throws BusinessException
*/
public function decreaseStock(int $skuId, int $quantity): void
{
$sku = ProductSku::find($skuId);

if (!$sku) {
throw new BusinessException('商品SKU不存在');
}

if ($sku->stock < $quantity) {
throw new BusinessException('库存不足');
}

// 使用乐观锁更新库存
$affected = ProductSku::where('id', $skuId)
->where('stock', '>=', $quantity)
->dec('stock', $quantity)
->update();

if (!$affected) {
throw new BusinessException('库存更新失败,请重试');
}

// 清除相关缓存
$this->clearProductCache($sku->product_id);
}

/**
* 增加库存
* @param int $skuId SKU ID
* @param int $quantity 数量
*/
public function increaseStock(int $skuId, int $quantity): void
{
$sku = ProductSku::find($skuId);

if ($sku) {
ProductSku::where('id', $skuId)->inc('stock', $quantity)->update();

// 清除相关缓存
$this->clearProductCache($sku->product_id);
}
}

/**
* 获取热门商品
* @param int $limit 数量限制
* @return array 热门商品列表
*/
public function getHotProducts(int $limit = 10): array
{
$cacheKey = "product:hot:{$limit}";
$products = Cache::get($cacheKey);

if (!$products) {
$products = $this->productRepository->query()
->where('status', Product::STATUS_ACTIVE)
->where('is_deleted', 0)
->order('sale_count', 'desc')
->order('view_count', 'desc')
->limit($limit)
->select()
->toArray();

// 缓存1小时
Cache::set($cacheKey, $products, 3600);
}

return $products;
}

/**
* 生成商品编码
* @return string 商品编码
*/
private function generateProductCode(): string
{
return 'P' . date('Ymd') . str_pad((string) mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
}

/**
* 获取商品SKU
* @param int $productId 商品ID
* @return array SKU列表
*/
private function getProductSkus(int $productId): array
{
return ProductSku::where('product_id', $productId)
->where('is_deleted', 0)
->select()
->toArray();
}

/**
* 创建商品SKU
* @param int $productId 商品ID
* @param array $skus SKU数据
*/
private function createProductSkus(int $productId, array $skus): void
{
foreach ($skus as $skuData) {
$skuData['product_id'] = $productId;
$skuData['code'] = $this->generateSkuCode();
ProductSku::create($skuData);
}
}

/**
* 生成SKU编码
* @return string SKU编码
*/
private function generateSkuCode(): string
{
return 'SKU' . date('Ymd') . str_pad((string) mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
}

/**
* 增加浏览次数
* @param int $productId 商品ID
*/
private function incrementViewCount(int $productId): void
{
// 使用异步方式增加浏览次数,避免影响性能
$cacheKey = "product:view_increment:{$productId}";
$count = Cache::get($cacheKey, 0);
Cache::set($cacheKey, $count + 1, 300); // 5分钟

// 每10次浏览更新一次数据库
if (($count + 1) % 10 === 0) {
Product::where('id', $productId)->inc('view_count', 10)->update();
Cache::delete($cacheKey);
}
}

/**
* 清除商品缓存
* @param int $productId 商品ID
*/
private function clearProductCache(int $productId): void
{
Cache::delete("product:detail:{$productId}");
Cache::tag('product_list')->clear();
}

/**
* 获取商品图片
* @param int $productId 商品ID
* @return array 图片列表
*/
private function getProductImages(int $productId): array
{
// 这里可以实现获取商品图片的逻辑
return [];
}

/**
* 获取商品属性
* @param int $productId 商品ID
* @return array 属性列表
*/
private function getProductAttributes(int $productId): array
{
// 这里可以实现获取商品属性的逻辑
return [];
}

/**
* 保存商品图片
* @param int $productId 商品ID
* @param array $images 图片数据
*/
private function saveProductImages(int $productId, array $images): void
{
// 这里可以实现保存商品图片的逻辑
}

/**
* 保存商品属性
* @param int $productId 商品ID
* @param array $attributes 属性数据
*/
private function saveProductAttributes(int $productId, array $attributes): void
{
// 这里可以实现保存商品属性的逻辑
}

/**
* 更新商品SKU
* @param int $productId 商品ID
* @param array $skus SKU数据
*/
private function updateProductSkus(int $productId, array $skus): void
{
// 这里可以实现更新商品SKU的逻辑
}

/**
* 更新商品图片
* @param int $productId 商品ID
* @param array $images 图片数据
*/
private function updateProductImages(int $productId, array $images): void
{
// 这里可以实现更新商品图片的逻辑
}

/**
* 更新商品属性
* @param int $productId 商品ID
* @param array $attributes 属性数据
*/
private function updateProductAttributes(int $productId, array $attributes): void
{
// 这里可以实现更新商品属性的逻辑
}
}

代码规范与最佳实践

编码规范

创建docs/coding-standards.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# ThinkPHP项目编码规范

## 1. 基本规范

### 1.1 PSR规范
- 遵循PSR-1、PSR-2、PSR-4规范
- 使用PSR-12作为代码风格标准
- 严格遵循命名空间规范

### 1.2 文件规范
- PHP文件必须以`<?php`开头
- 文件编码必须是UTF-8
- 文件末尾不要有`?>`标签
- 每行代码长度不超过120个字符

### 1.3 命名规范
- 类名使用大驼峰命名法(PascalCase)
- 方法名使用小驼峰命名法(camelCase)
- 变量名使用小驼峰命名法(camelCase)
- 常量名使用全大写加下划线(UPPER_CASE)
- 数据库表名使用下划线分隔(snake_case)

## 2. 类设计规范

### 2.1 单一职责原则
每个类只负责一个功能领域,避免类过于庞大。

### 2.2 依赖注入
优先使用构造函数注入,避免在类内部直接实例化依赖。

### 2.3 接口隔离
定义清晰的接口,实现类只依赖需要的接口方法。

## 3. 方法设计规范

### 3.1 方法长度
单个方法不超过50行,复杂逻辑拆分为多个私有方法。

### 3.2 参数数量
方法参数不超过5个,多参数使用数组或对象传递。

### 3.3 返回值
明确方法返回值类型,使用类型声明。

## 4. 注释规范

### 4.1 类注释
```php
/**
* 用户服务类
* 处理用户相关的业务逻辑
*
* @author 开发者姓名
* @since 1.0.0
*/
class UserService
{
}

4.2 方法注释

1
2
3
4
5
6
7
8
9
10
/**
* 用户登录
* @param string $account 账号(邮箱或手机号)
* @param string $password 密码
* @return array 登录结果
* @throws BusinessException 业务异常
*/
public function login(string $account, string $password): array
{
}

5. 异常处理规范

5.1 异常分类

  • 业务异常:BusinessException
  • 验证异常:ValidateException
  • 系统异常:SystemException

5.2 异常信息

异常信息要清晰明确,便于定位问题。

6. 数据库规范

6.1 表设计

  • 表名使用复数形式
  • 主键统一使用id
  • 创建时间字段:created_at
  • 更新时间字段:updated_at
  • 软删除字段:deleted_at

6.2 查询优化

  • 避免使用SELECT *
  • 合理使用索引
  • 避免N+1查询问题

7. 缓存规范

7.1 缓存键命名

使用冒号分隔的层级结构:模块:功能:标识

7.2 缓存时间

  • 热点数据:5-30分钟
  • 一般数据:1-6小时
  • 静态数据:1-24小时

8. 日志规范

8.1 日志级别

  • DEBUG:调试信息
  • INFO:一般信息
  • WARNING:警告信息
  • ERROR:错误信息
  • CRITICAL:严重错误

8.2 日志格式

使用结构化日志,包含必要的上下文信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

### 性能优化最佳实践

创建`docs/performance-best-practices.md`:

```markdown
# ThinkPHP性能优化最佳实践

## 1. 数据库优化

### 1.1 查询优化
- 使用合适的索引
- 避免全表扫描
- 优化JOIN查询
- 使用EXPLAIN分析查询计划

### 1.2 连接池优化
- 配置合适的连接池大小
- 设置连接超时时间
- 监控连接使用情况

### 1.3 读写分离
- 主库处理写操作
- 从库处理读操作
- 处理主从延迟问题

## 2. 缓存优化

### 2.1 多级缓存
- 浏览器缓存
- CDN缓存
- 应用缓存
- 数据库缓存

### 2.2 缓存策略
- Cache-Aside模式
- Write-Through模式
- Write-Behind模式

### 2.3 缓存失效
- 设置合理的过期时间
- 实现缓存预热
- 处理缓存雪崩和穿透

## 3. 代码优化

### 3.1 算法优化
- 选择合适的数据结构
- 优化循环逻辑
- 减少不必要的计算

### 3.2 内存优化
- 及时释放大对象
- 避免内存泄漏
- 使用生成器处理大数据

### 3.3 I/O优化
- 批量处理数据
- 异步处理耗时操作
- 使用连接池

## 4. 架构优化

### 4.1 微服务架构
- 服务拆分
- 服务治理
- 负载均衡

### 4.2 消息队列
- 异步处理
- 削峰填谷
- 系统解耦

### 4.3 CDN加速
- 静态资源加速
- 动态内容加速
- 全球节点部署

## 5. 监控与调优

### 5.1 性能监控
- 响应时间监控
- 吞吐量监控
- 错误率监控
- 资源使用监控

### 5.2 性能分析
- 慢查询分析
- 内存使用分析
- CPU使用分析
- 网络延迟分析

### 5.3 持续优化
- 定期性能评估
- 瓶颈识别与解决
- 容量规划

项目部署与运维

自动化部署脚本

创建scripts/deploy.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#!/bin/bash

# ThinkPHP项目自动化部署脚本
# 支持多环境部署和回滚

set -e

# 配置变量
PROJECT_NAME="ecommerce-system"
GIT_REPO="https://github.com/your-org/ecommerce-system.git"
DEPLOY_PATH="/var/www/html"
BACKUP_PATH="/var/backups/deployments"
PHP_VERSION="8.2"
NODE_VERSION="18"

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}

log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}

log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}

# 检查环境
check_environment() {
log_info "检查部署环境..."

# 检查PHP版本
if ! command -v php &> /dev/null; then
log_error "PHP未安装"
exit 1
fi

PHP_CURRENT=$(php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;")
if [[ "$PHP_CURRENT" < "$PHP_VERSION" ]]; then
log_warn "PHP版本过低,当前:$PHP_CURRENT,要求:$PHP_VERSION"
fi

# 检查Composer
if ! command -v composer &> /dev/null; then
log_error "Composer未安装"
exit 1
fi

# 检查Git
if ! command -v git &> /dev/null; then
log_error "Git未安装"
exit 1
fi

log_info "环境检查完成"
}

# 备份当前版本
backup_current() {
log_info "备份当前版本..."

if [ -d "$DEPLOY_PATH" ]; then
BACKUP_NAME="backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_PATH"
cp -r "$DEPLOY_PATH" "$BACKUP_PATH/$BACKUP_NAME"
log_info "备份完成:$BACKUP_PATH/$BACKUP_NAME"
fi
}

# 下载代码
download_code() {
log_info "下载最新代码..."

TEMP_PATH="/tmp/${PROJECT_NAME}_$(date +%Y%m%d_%H%M%S)"
git clone "$GIT_REPO" "$TEMP_PATH"

if [ $? -ne 0 ]; then
log_error "代码下载失败"
exit 1
fi

log_info "代码下载完成:$TEMP_PATH"
}

# 安装依赖
install_dependencies() {
log_info "安装项目依赖..."

cd "$TEMP_PATH"

# 安装PHP依赖
composer install --no-dev --optimize-autoloader

if [ $? -ne 0 ]; then
log_error "PHP依赖安装失败"
exit 1
fi

# 安装Node.js依赖(如果存在package.json)
if [ -f "package.json" ]; then
npm ci --production
npm run build
fi

log_info "依赖安装完成"
}

# 配置文件处理
handle_config() {
log_info "处理配置文件..."

# 复制环境配置
if [ -f "$DEPLOY_PATH/.env" ]; then
cp "$DEPLOY_PATH/.env" "$TEMP_PATH/.env"
fi

# 设置文件权限
chmod -R 755 "$TEMP_PATH"
chmod -R 777 "$TEMP_PATH/runtime"

if [ -d "$TEMP_PATH/public/uploads" ]; then
chmod -R 777 "$TEMP_PATH/public/uploads"
fi

log_info "配置文件处理完成"
}

# 数据库迁移
run_migrations() {
log_info "执行数据库迁移..."

cd "$TEMP_PATH"

# 执行数据库迁移
php think migrate:run

if [ $? -ne 0 ]; then
log_warn "数据库迁移执行失败,请手动检查"
fi

log_info "数据库迁移完成"
}

# 部署代码
deploy_code() {
log_info "部署代码..."

# 停止服务(如果使用Supervisor管理队列)
if command -v supervisorctl &> /dev/null; then
supervisorctl stop all
fi

# 原子性部署
if [ -d "$DEPLOY_PATH" ]; then
mv "$DEPLOY_PATH" "${DEPLOY_PATH}_old"
fi

mv "$TEMP_PATH" "$DEPLOY_PATH"

# 重启服务
if command -v supervisorctl &> /dev/null; then
supervisorctl start all
fi

# 重启PHP-FPM
if command -v systemctl &> /dev/null; then
systemctl reload php-fpm
fi

# 重启Nginx
if command -v systemctl &> /dev/null; then
systemctl reload nginx
fi

log_info "代码部署完成"
}

# 清理缓存
clear_cache() {
log_info "清理应用缓存..."

cd "$DEPLOY_PATH"

# 清理ThinkPHP缓存
php think clear

# 清理OPcache
if command -v php &> /dev/null; then
php -r "if(function_exists('opcache_reset')) opcache_reset();"
fi

log_info "缓存清理完成"
}

# 健康检查
health_check() {
log_info "执行健康检查..."

# 检查应用是否正常响应
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/health)

if [ "$HTTP_CODE" = "200" ]; then
log_info "健康检查通过"
else
log_error "健康检查失败,HTTP状态码:$HTTP_CODE"
rollback
exit 1
fi
}

# 回滚
rollback() {
log_warn "开始回滚..."

if [ -d "${DEPLOY_PATH}_old" ]; then
rm -rf "$DEPLOY_PATH"
mv "${DEPLOY_PATH}_old" "$DEPLOY_PATH"

# 重启服务
if command -v systemctl &> /dev/null; then
systemctl reload php-fpm
systemctl reload nginx
fi

log_info "回滚完成"
else
log_error "没有找到备份版本,无法回滚"
fi
}

# 清理
cleanup() {
log_info "清理临时文件..."

if [ -d "${DEPLOY_PATH}_old" ]; then
rm -rf "${DEPLOY_PATH}_old"
fi

# 清理旧备份(保留最近10个)
if [ -d "$BACKUP_PATH" ]; then
cd "$BACKUP_PATH"
ls -t | tail -n +11 | xargs -r rm -rf
fi

log_info "清理完成"
}

# 主函数
main() {
log_info "开始部署 $PROJECT_NAME"

check_environment
backup_current
download_code
install_dependencies
handle_config
run_migrations
deploy_code
clear_cache
health_check
cleanup

log_info "部署完成!"
}

# 处理命令行参数
case "$1" in
"deploy")
main
;;
"rollback")
rollback
;;
"health")
health_check
;;
*)
echo "用法: $0 {deploy|rollback|health}"
echo " deploy - 部署最新版本"
echo " rollback - 回滚到上一版本"
echo " health - 健康检查"
exit 1
;;
esac

总结

通过本系列十五篇文章的学习,我们全面掌握了ThinkPHP6/8的核心技术和最佳实践:

技术栈覆盖

  1. 基础架构:MVC模式、路由系统、中间件
  2. 数据处理:ORM操作、数据库优化、模型关联
  3. 用户体验:表单验证、文件上传、模板引擎
  4. 系统集成:RESTful API、第三方服务、支付集成
  5. 高级特性:事件系统、缓存优化、队列处理
  6. 现代化部署:容器化、微服务、性能监控

最佳实践要点

  1. 代码质量

    • 遵循PSR规范
    • 单元测试覆盖
    • 代码审查机制
    • 持续集成部署
  2. 性能优化

    • 数据库查询优化
    • 多级缓存策略
    • 异步任务处理
    • 资源合理配置
  3. 安全防护

    • 输入验证过滤
    • SQL注入防护
    • XSS攻击防护
    • CSRF令牌验证
  4. 运维监控

    • 日志收集分析
    • 性能指标监控
    • 异常告警机制
    • 自动化部署

发展建议

  1. 持续学习:关注ThinkPHP官方更新,学习新特性
  2. 实践应用:在实际项目中应用所学知识
  3. 社区参与:参与开源项目,分享经验
  4. 技术拓展:学习相关技术栈,提升综合能力

ThinkPHP作为优秀的PHP框架,为开发者提供了强大的工具和灵活的架构。掌握这些技术和最佳实践,将帮助您构建高质量、高性能的企业级应用系统。

本站由 提供部署服务