yii默认activecontroller不适用于关注/取关,因其仅提供crud模板,而关注是需校验合法性、事务保障和状态一致性的语义化操作,须手写actionfollow/actionunfollow并合理设计rest路由与缓存。

Yii 框架的 RESTful 接口完全适合社交类应用,但直接套用默认 ActiveController 处理用户关系(如关注、互粉、黑名单)会很快遇到权限、状态一致性、性能三重卡点。
为什么默认 ActiveController 不能直接用于关注/取关
它只提供 CRUD 模板,而“关注”不是资源创建,是带语义的状态变更操作:
-
POST /users/123/follow不是创建UserFollow资源,而是检查双方状态、更新时间戳、触发通知——ActiveController::actionCreate()无法自然表达这种逻辑 - 默认不校验操作合法性:用户 A 关注自己、重复关注、对已拉黑用户发起关注,都会绕过验证直接写库
- 没有内置事务包裹:关注成功但通知失败,会导致数据与业务状态不一致
actionFollow() 和 actionUnfollow() 必须手写
把关注逻辑收束到独立动作里,才能控制流程和边界。示例结构:
public function actionFollow($id)
{
$target = $this->findModel($id);
$follower = Yii::$app->user->identity;
if ($follower->id == $target->id) {
throw new BadRequestHttpException('Cannot follow yourself');
}
$relation = UserFollow::find()
->where(['follower_id' => $follower->id, 'followee_id' => $target->id])
->one();
if ($relation) {
throw new BadRequestHttpException('Already following');
}
$relation = new UserFollow([
'follower_id' => $follower->id,
'followee_id' => $target->id,
'created_at' => time(),
]);
// 必须显式开启事务
$transaction = Yii::$app->db->beginTransaction();
try {
$relation->save();
// 可选:发消息、更新粉丝数缓存等
$transaction->commit();
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
}
}
注意:$id 是被关注用户的 ID,不是当前登录用户 ID;路由需配置为 'POST follow' => 'follow',而非依赖自动映射。
RESTful 路由设计要区分「资源」和「动作」
别强行把所有行为塞进 /users/{id}。更清晰的划分:
-
GET /users/{id}/followers→ 获取粉丝列表(可复用ActiveController的actionIndex,但需重写dataProvider) -
GET /users/{id}/following→ 获取关注列表 -
POST /users/{id}/follow→ 手写actionFollow -
DELETE /users/{id}/follow→ 手写actionUnfollow -
GET /users/me/relationships→ 当前用户与目标用户的关系快照(是否互关、是否拉黑),这个接口根本不需要对应模型,纯查表组合
混用资源路径和动作路径是常见误区,Yii 的 UrlRule 支持 extraPatterns 精确控制,别怕多配几条。
缓存和并发问题比想象中早出现
社交关系读多写少,但“是否已关注”这类判断每页请求都可能调用多次。不加缓存,单次首页加载就可能触发十几次 UserFollow::find()->exists() 查询。
建议在 UserFollow 模型层封装带缓存的检查方法:
public static function isFollowing($followerId, $followeeId)
{
$key = "uf:{$followerId}:{$followeeId}";
$cached = Yii::$app->cache->get($key);
if ($cached !== false) {
return $cached;
}
$exists = self::find()
->where(['follower_id' => $followerId, 'followee_id' => $followeeId])
->exists();
Yii::$app->cache->set($key, $exists, 3600); // 缓存 1 小时
return $exists;
}
注意:缓存失效必须在 follow/unfollow 后主动清除,不能只靠 TTL;高并发下考虑用 Lua 脚本保证原子性,否则可能缓存击穿。











