免费资源下载
作者:技术架构师 | 发布日期:2023年10月
一、项目架构设计理念
在现代Web开发中,API服务已成为系统间通信的核心。本次我们将基于ThinkPHP 6.0构建一个电商平台的评论系统,重点展示多态关联在实际业务中的应用。
1.1 分层架构设计
app/
├── controller/ # 控制器层
├── service/ # 业务服务层(新增)
├── model/ # 数据模型层
├── repository/ # 数据仓库层(新增)
├── validate/ # 验证器层
└── middleware/ # 中间件层
二、多态关联模型实战
2.1 业务场景分析
电商系统中,评论功能需要支持多种类型的目标对象:商品、文章、视频等。传统的外键关联无法满足这种动态关联需求,这正是多态关联的应用场景。
2.2 数据表设计
-- 评论表
CREATE TABLE `comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`content` text NOT NULL COMMENT '评论内容',
`user_id` int(11) NOT NULL COMMENT '用户ID',
`commentable_type` varchar(50) NOT NULL COMMENT '关联模型类型',
`commentable_id` int(11) NOT NULL COMMENT '关联模型ID',
`created_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 商品表
CREATE TABLE `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '商品名称',
`price` decimal(10,2) NOT NULL COMMENT '价格',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 文章表
CREATE TABLE `articles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL COMMENT '文章标题',
`content` text NOT NULL COMMENT '文章内容',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.3 模型定义
// app/model/Comment.php
namespace appmodel;
use thinkModel;
class Comment extends Model
{
// 定义多态关联
public function commentable()
{
return $this->morphTo();
}
// 关联用户
public function user()
{
return $this->belongsTo(User::class);
}
}
// app/model/Product.php
class Product extends Model
{
// 定义反向多态关联
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
// app/model/Article.php
class Article extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
三、服务层设计与实现
3.1 创建评论服务
// app/service/CommentService.php
namespace appservice;
use appmodelComment;
use apprepositoryCommentRepository;
use appvalidateCommentValidate;
use thinkexceptionValidateException;
class CommentService
{
protected $repository;
public function __construct(CommentRepository $repository)
{
$this->repository = $repository;
}
/**
* 添加评论
* @param array $data 评论数据
* @return Comment
* @throws Exception
*/
public function addComment(array $data): Comment
{
// 数据验证
validate(CommentValidate::class)->check($data);
// 业务逻辑处理
$data['user_id'] = request()->userId; // 从JWT中获取
// 使用仓库模式保存
$comment = $this->repository->create($data);
// 触发评论添加事件
event('CommentAdded', $comment);
return $comment;
}
/**
* 获取目标对象的评论列表(支持分页)
* @param string $type 模型类型
* @param int $id 模型ID
* @param int $page 页码
* @param int $limit 每页数量
* @return array
*/
public function getComments(string $type, int $id, int $page = 1, int $limit = 15): array
{
$modelClass = $this->getModelClass($type);
if (!class_exists($modelClass)) {
throw new Exception('不支持的评论目标类型');
}
$model = $modelClass::find($id);
if (!$model) {
throw new Exception('目标对象不存在');
}
// 使用with预加载用户信息
$comments = $model->comments()
->with(['user' => function($query) {
$query->field('id,username,avatar');
}])
->order('created_at', 'desc')
->paginate([
'page' => $page,
'list_rows' => $limit
]);
return [
'list' => $comments->items(),
'total' => $comments->total(),
'page' => $comments->currentPage(),
'limit' => $comments->listRows()
];
}
/**
* 获取模型类名
* @param string $type
* @return string
*/
private function getModelClass(string $type): string
{
$map = [
'product' => 'appmodelProduct',
'article' => 'appmodelArticle',
'video' => 'appmodelVideo',
];
return $map[$type] ?? '';
}
}
3.2 数据仓库层
// app/repository/CommentRepository.php
namespace apprepository;
use appmodelComment;
use thinkfacadeDb;
class CommentRepository
{
/**
* 创建评论(使用事务)
* @param array $data
* @return Comment
* @throws Exception
*/
public function create(array $data): Comment
{
Db::startTrans();
try {
$comment = Comment::create($data);
// 更新目标对象的评论计数
$this->updateCommentCount($data['commentable_type'], $data['commentable_id']);
Db::commit();
return $comment;
} catch (Exception $e) {
Db::rollback();
throw $e;
}
}
/**
* 更新评论计数
* @param string $type
* @param int $id
*/
private function updateCommentCount(string $type, int $id): void
{
$table = $this->getTableName($type);
$count = Comment::where([
'commentable_type' => $type,
'commentable_id' => $id
])->count();
Db::table($table)
->where('id', $id)
->update(['comment_count' => $count]);
}
private function getTableName(string $type): string
{
return $type . 's'; // 简单映射,实际应根据配置
}
}
四、控制器与API设计
4.1 RESTful API控制器
// app/controller/api/v1/Comment.php
namespace appcontrollerapiv1;
use appserviceCommentService;
use thinkfacadeRequest;
use thinkResponse;
class Comment
{
protected $service;
public function __construct(CommentService $service)
{
$this->service = $service;
}
/**
* 添加评论
* @return Response
*/
public function create(): Response
{
$data = Request::only([
'content',
'commentable_type',
'commentable_id'
]);
try {
$comment = $this->service->addComment($data);
return json([
'code' => 200,
'message' => '评论成功',
'data' => $comment->hidden(['user_id', 'updated_at'])
]);
} catch (Exception $e) {
return json([
'code' => 500,
'message' => $e->getMessage()
], 500);
}
}
/**
* 获取评论列表
* @param string $type
* @param int $id
* @return Response
*/
public function index(string $type, int $id): Response
{
$page = Request::param('page', 1);
$limit = Request::param('limit', 15);
try {
$result = $this->service->getComments($type, $id, $page, $limit);
return json([
'code' => 200,
'message' => 'success',
'data' => $result
]);
} catch (Exception $e) {
return json([
'code' => 404,
'message' => $e->getMessage()
], 404);
}
}
}
4.2 路由配置
// route/api.php
use thinkfacadeRoute;
// 评论API
Route::group('api/v1', function() {
// 获取评论列表
Route::get('comments/:type/:id', 'api/v1.Comment/index');
// 添加评论
Route::post('comments', 'api/v1.Comment/create');
// 删除评论
Route::delete('comments/:id', 'api/v1.Comment/delete');
})->allowCrossDomain();
五、高级特性与优化
5.1 使用观察者模式
// app/observer/CommentObserver.php
namespace appobserver;
use appmodelComment;
use thinkfacadeCache;
class CommentObserver
{
/**
* 新增评论后清除缓存
* @param Comment $comment
*/
public function afterInsert(Comment $comment): void
{
$cacheKey = sprintf(
'comments_%s_%d',
$comment->commentable_type,
$comment->commentable_id
);
Cache::delete($cacheKey);
// 异步处理通知
queue('appjobNotifyJob', [
'type' => 'comment',
'data' => $comment->toArray()
]);
}
/**
* 删除评论前检查权限
* @param Comment $comment
* @return bool
*/
public function beforeDelete(Comment $comment): bool
{
$userId = request()->userId;
// 只能删除自己的评论或管理员
if ($comment->user_id != $userId && !$this->isAdmin($userId)) {
return false;
}
return true;
}
}
5.2 性能优化策略
// 使用Redis缓存评论列表
public function getCachedComments(string $type, int $id): array
{
$cacheKey = sprintf('comments_%s_%d', $type, $id);
return Cache::remember($cacheKey, 300, function() use ($type, $id) {
return $this->getComments($type, $id);
});
}
// 数据库查询优化
public function getCommentsWithOptimization(string $type, int $id): array
{
return Comment::where([
'commentable_type' => $type,
'commentable_id' => $id
])
->with([
'user' => function($query) {
$query->cache(60)->field('id,username,avatar');
}
])
->field('id,content,user_id,created_at')
->order('created_at', 'desc')
->select()
->toArray();
}
六、测试用例
6.1 单元测试示例
// tests/CommentServiceTest.php
namespace tests;
use appserviceCommentService;
use thinktestingTestCase;
class CommentServiceTest extends TestCase
{
protected $service;
protected function setUp(): void
{
parent::setUp();
$this->service = app(CommentService::class);
}
/**
* 测试添加评论
*/
public function testAddComment(): void
{
$data = [
'content' => '测试评论内容',
'commentable_type' => 'product',
'commentable_id' => 1,
];
$comment = $this->service->addComment($data);
$this->assertInstanceOf('appmodelComment', $comment);
$this->assertEquals('测试评论内容', $comment->content);
$this->assertEquals('product', $comment->commentable_type);
}
/**
* 测试获取评论列表
*/
public function testGetComments(): void
{
$result = $this->service->getComments('product', 1);
$this->assertIsArray($result);
$this->assertArrayHasKey('list', $result);
$this->assertArrayHasKey('total', $result);
}
}
七、部署与监控
7.1 Docker部署配置
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:80"
volumes:
- ./app:/var/www/html/app
- ./runtime:/var/www/html/runtime
environment:
- APP_DEBUG=false
- DB_HOST=mysql
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: thinkphp_app
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
mysql_data:
7.2 性能监控配置
// config/trace.php
return [
// 开启SQL监听
'sql' => true,
// 慢查询阈值(毫秒)
'slow_sql_time' => 1000,
// 请求追踪
'request' => true,
// 内存使用监控
'memory' => true,
];
// 使用中间件记录请求日志
class RequestLogMiddleware
{
public function handle($request, Closure $next)
{
$startTime = microtime(true);
$startMemory = memory_get_usage();
$response = $next($request);
$endTime = microtime(true);
$endMemory = memory_get_usage();
// 记录到日志或监控系统
Log::write(sprintf(
'请求: %s | 耗时: %.2fms | 内存: %.2fMB',
$request->url(),
($endTime - $startTime) * 1000,
($endMemory - $startMemory) / 1024 / 1024
));
return $response;
}
}

