基于Laravel实现的用户动态模块开发


Posted in PHP onSeptember 21, 2017

前言

相信大家都知道,几乎所有的社区应用都有用户动态这个部分,用户可以通过好友动态获能取到更多感兴趣的内容,从而提高社区活跃度和用户粘性。它的实现相对来讲比普通的内容发布要复杂一些,主要体现在内容多样性上。

为了解决这个问题,我们得把这些不同类型的内容抽象,提取共性,使用相同的结构来处理,开发起来就会简单很多。

概念抽象

用户动态,顾名思义,动态的产生,就是一系列事件的历史记录,所以首先关注“事件”这个名词,它有哪些属性:

  • 触发者,基于社区所有的事件几乎都是由用户触发的
  • 事件主体,事件的主体信息,例如“xxx发布了文章” 中的 “文章”。
  • 事件属性,事件主体不同,所需要的附加信息也不同,比如事件类型。
  • 发生时间,记录事件产生的时间,当然了在我们的数据库通常记录了所有数据产生的时间。

我们将用户动态抽象成只有 4 个基础属性的结构,就比较容易实现了:

- description    事件描述
- causer_id 或者 user_id 事件触发者
- subject_id    主体 ID
- subject_type   主体类型
- properties    事件附加属性
- created_at    事件产生时间

而主体部分就是 Laravel 里的 morph relation, 多态关联。

怎么展示

我们的动态展示需求通常有以下几种:

  • 我的好友的动态
  • 某个人的动态,通常是个人中心
  • 全部动态,比如 Laravel China 首页的全部动态
  • 动态搜索,比较少见

我最近正在开发 EasyWeChat 新版网站,其中也有用户动态,举例:

xxx 发布了讨论 《请问大家怎么使用 xxx》
xxx 评论了 xxx 的话题 《请问大家怎么使用 xxx》
xxx 回复了 xxx 的评论 “我是按照文档上 ...”
xxx 购买了 《微信开发:自定义菜单的使用》
xxx 关注了 xxx
...

你会发现,基本上每种动态的写法都不一样,所以我们还需要记录一个 “事件类型” ,比如 “关注”、 “发布”、“回复”、“购买”。

然后我们在 blade 或者其它模板引擎的使用中,就可以 switch ... case 写法,来应用不同的模板渲染这些样式,比如 blade 中,我的用法:

@switch($activity->properties['event'] ?? '')
 @case('discussion.created')
  ...
  @break
 @case('comment.created')
  ...
  @break
@endswitch

代码实现

前面我们已经讨论完了数据存储以及展示方面的设计,接着就是怎么实现,如果你比较勤劳,可以原生实现,毕竟上面的实现方法已经描述清晰,写点代码实现就搞定了,今天我要推荐的是使用 spatie/laravel-activitylog 来实现:

安装一直很简单对吧:

$ composer install spatie/laravel-activitylog -vvv

记录动态

activity()->log('Look, I logged something');

当然了这种记录没意义,几乎没有任何有用的信息,所以我们通常的用法应该是这样:

activity()
 ->performedOn($anEloquentModel)
 ->causedBy($user)
 ->withProperties(['customProperty' => 'customValue'])
 ->log('Look, I logged something');
 
$lastLoggedActivity = Activity::all()->last();

$lastLoggedActivity->subject; //returns an instance of an eloquent model
$lastLoggedActivity->causer; //returns an instance of your user model
$lastLoggedActivity->getExtraProperty('customProperty'); //returns 'customValue'
$lastLoggedActivity->description; //returns 'Look, I logged something'

方法介绍:

  • performedOn($model) 设置事件主体,也就是 Eloquent Model 实例
  • causedBy($user) 设置事件触发者, User 实例
  • withProperties($properties) 上面我们概念里的事件属性
  • withProperty($key, $value) 事件属性的单个用法
  • log($description) 事件描述

比如,我们要记录一条,用户发布了讨论:

$discussion = App\Discussion::create([...]);

activity()->on($discussion)
->withProperty('event', 'discussion.created')
->log('发表了话题');

或者用户注册时,我要记录一条动态:

activity()->on($user)
->withProperty('event', 'user.created')
->log('加入 EasyWeChat');

你会发现我都没有设置触发者,因为这个模块如果你没设置触发者默认就是当前登录用户。

展示动态

展示动态就是根据条件从数据库拿出来,这里使用包提供的模型类:Spatie\Activitylog\Models\Activity

use Spatie\Activitylog\Models\Activity;

// 全部动态
$activities = Activity::all();
// 用户 ID 为 2 的动态 
$activities = Activity::causedBy(User::find(2))->paginate(15);
// 以文章 ID 为 13 为主体的动态
$activities = Activity::forSubject(Post::find(13))->paginate(15);

接着就是遍历展示就好了。

一些经验与技巧

设置一个专门的动态观察者类来记录动态

$ ./artisan make:listener UserActivitySubscriber

代码如下:

<?php 

namespace App\Listeners;

class UserActivitySubscriber
{
 protected $lisen = [
  'eloquent.created: App\User' => 'onUserCreated',
  'eloquent.created: App\Discussion' => 'onDiscussionCreated',
 ];

 public function subscribe($events)
 {
  foreach ($this->lisen as $event => $listener) {
   $events->lisen($event, __CLASS__.'@'.$listener);
  }
 }

 public function onUserCreated($user)
 {
  activity()->on($user)
   ->withProperty('event', 'user.created')
   ->log('加入 EasyWeChat');
 }

 public function onDiscussionCreated($discussion)
 {
  activity()->on($discussion)
    ->withProperty('event', 'discussion.created')->log('发表了话题');
 }
}

然后我们去注册这个订阅类:

在 App\Providers\EventServiceProvider 中 $subscribe 中注册这个订阅类:

/**
 * @var array
 */
protected $subscribe = [
 \App\Listeners\UserActivitySubscriber::class,
];

上面我们利用了 Eloquent 模型事件来监听模型的变化,当各种模型事件创建的时候我们调用对应的方法来记录动态,所以实现起来非常的方便。

在事件属性里记录关键信息

看到上面记录动态的时候你可能会问,只存储了 ID,这种多态关联,查询的时候会压力很大,比如,我们要将动态显示为:

安小超 发布了文章 《自定义菜单的使用》

我们如果只是存储了文章的 id 与类型,我们还需要查询一次文章表,才能得到标题用于显示,这样一个动态列表的话,可能会几十条 SQL 了,的确是这样的,我的解决方案是这样的:

其实我们的用户动态是不要求 100% 精准的,所以,我如果在记录时把文章的标题一起存下来是不是就不用再查表了?其实就是,我们在动态列表需要展示的关键信息,比如标题这些一起用 withProperties 存起来,这样就一条 SQL 解决了动态列表问题。

这样的做法也有弊端,比如文章改了标题的时候,这里就不同步了,当然你也可以在文章修改时来改这个属性,不过我个人认为没有多大必要。毕竟动态就是记录了当时的情况,后来改标题了并没有什么问题。

OK,用户动态模块的开发就分享到这里,如果你有更高级的实现欢迎随时交流。

关于好友动态部分的实现,根据你的应用量级,以及好友关系的存储各有不同,大家自己集思广益即可,大部分都是先查好友关系再查动态,关联查询也可以,自己实现吧。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
第三节 定义一个类 [3]
Oct 09 PHP
PHP实现Socket服务器的代码
Apr 03 PHP
php缩小png图片不损失透明色的解决方法
Dec 25 PHP
php检测iis环境是否支持htaccess的方法
Feb 18 PHP
PHPThumb图片处理实例
May 03 PHP
初识Laravel
Oct 30 PHP
PHP中余数、取余的妙用
Jun 29 PHP
Symfony模板的快捷变量用法实例
Mar 17 PHP
php中preg_replace正则替换用法分析【一次替换多个值】
Jan 17 PHP
PHP基于递归实现的约瑟夫环算法示例
Aug 27 PHP
PHP中in_array的隐式转换的解决方法
Mar 06 PHP
laravel 5.4 + vue + vux + element的环境搭配过程介绍
Apr 26 PHP
PHP调用API接口实现天气查询功能的示例
Sep 21 #PHP
PHP判断json格式是否正确的实现代码
Sep 20 #PHP
Yii2.0使用阿里云OSS的SDK上传图片、下载、删除图片示例
Sep 20 #PHP
PHP文件管理之实现网盘及压缩包的功能操作
Sep 20 #PHP
弹出模态框modal的实现方法及实例
Sep 19 #PHP
PHP 进度条函数的简单实例
Sep 19 #PHP
yii2.0整合阿里云oss删除单个文件的方法
Sep 19 #PHP
You might like
无数据库的详细域名查询程序PHP版(3)
2006/10/09 PHP
php实现的遍历文件夹下所有文件,编辑删除
2010/01/05 PHP
Could not load type System.ServiceModel.Activation.HttpModule解决办法
2012/12/29 PHP
护卫神php套件 php版本升级方法(php5.5.24)
2015/05/10 PHP
简单谈谈PHP中的include、include_once、require以及require_once语句
2016/04/23 PHP
PHP count_chars()函数讲解
2019/02/14 PHP
详解提高使用Java反射的效率方法
2019/04/29 PHP
jQuery将所有被选中的checkbox某个属性值连接成字符串的方法
2015/01/24 Javascript
javascript中setAttribute()函数使用方法及兼容性
2015/07/19 Javascript
jQuery插件开发精品教程让你的jQuery提升一个台阶
2016/01/27 Javascript
全面解析JavaScript中apply和call以及bind(推荐)
2016/06/15 Javascript
js简单获取表单中单选按钮值的方法
2016/08/23 Javascript
Bootstrap使用基础教程详解
2016/09/05 Javascript
ion content 滚动到底部会遮住一部分视图的快速解决方法
2016/09/06 Javascript
浅谈layer的iframe弹窗给里面的标签赋值的问题
2016/11/10 Javascript
JS异步文件分片断点上传的实现思路
2016/12/25 Javascript
微信小程序项目实践之九宫格实现及item跳转功能
2018/07/19 Javascript
JS高级技巧(简洁版)
2018/07/29 Javascript
vue实现购物车抛物线小球动画效果的方法详解
2019/02/13 Javascript
vue style width a href动态拼接问题的解决
2020/08/07 Javascript
python利用elaphe制作二维条形码实现代码
2012/05/25 Python
python将文本转换成图片输出的方法
2015/04/28 Python
Python基于有道实现英汉字典功能
2015/07/25 Python
浅谈python爬虫使用Selenium模拟浏览器行为
2018/02/23 Python
Python 十六进制整数与ASCii编码字符串相互转换方法
2018/07/09 Python
PyQt Qt Designer工具的布局管理详解
2019/08/07 Python
解决python gdal投影坐标系转换的问题
2020/01/17 Python
html5基础标签(html5视频标签 html5新标签用法)
2013/12/30 HTML / CSS
Marriott国际:万豪国际酒店查询预订
2017/09/25 全球购物
欧洲最大的球衣网上商店:Kitbag
2017/11/11 全球购物
CheapTickets泰国:廉价航班,查看促销价格并预订机票
2019/12/28 全球购物
英国排名第一的宠物店:PetPlanet
2020/02/02 全球购物
纪检监察建议书
2014/05/19 职场文书
业绩倒数第一的检讨书
2014/09/24 职场文书
优秀的商业计划书,让融资一步到位
2019/05/07 职场文书
利用Python脚本写端口扫描器socket,python-nmap
2022/07/23 Python