基于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 相关文章推荐
基于HTTP长连接的&quot;服务器推&quot;技术的php 简易聊天室
Oct 31 PHP
PHP 删除文件与文件夹操作 unlink()与rmdir()这两个函数的使用
Jul 17 PHP
PHP 小心urldecode引发的SQL注入漏洞
Oct 27 PHP
PHP中使用foreach和引用导致程序BUG的问题介绍
Sep 05 PHP
PHP基于php_imagick_st-Q8.dll实现JPG合成GIF图片的方法
Jul 11 PHP
33道php常见面试题及答案
Jul 06 PHP
php中文字符串截取多种方法汇总
Oct 06 PHP
php封装的数据库函数与用法示例【参考thinkPHP】
Nov 08 PHP
php常用数组array函数实例总结【赋值,拆分,合并,计算,添加,删除,查询,判断,排序】
Dec 07 PHP
PHP中OpenSSL加密问题整理
Dec 14 PHP
PHP读取文件,解决中文乱码UTF-8的方法分析
Jan 22 PHP
yii框架结合charjs统计上一年与当前年数据的方法示例
Apr 04 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 默默经典版本
2009/08/04 PHP
PHP输出九九乘法表代码实例
2015/03/27 PHP
PHP中file_put_contents追加和换行的实现方法
2017/04/01 PHP
PHP中一个有趣的preg_replace函数详解
2018/08/15 PHP
理解JavaScript的caller,callee,call,apply
2009/04/28 Javascript
JQuyer $.post 与 $.ajax 访问WCF ajax service 时的问题需要注意的地方
2011/09/20 Javascript
jquery实现excel导出的方法
2013/04/04 Javascript
javascript 弹出的窗口返回值给父窗口具体实现
2013/11/23 Javascript
javascript笛卡尔积算法实现方法
2015/04/08 Javascript
JavaScript在Android的WebView中parseInt函数转换不正确问题解决方法
2015/04/25 Javascript
jquery读取xml文件实现省市县三级联动的方法
2015/05/29 Javascript
常常会用到的截取字符串substr()、substring()、slice()方法详解
2015/12/16 Javascript
Centos7 中 Node.js安装简单方法
2016/11/02 Javascript
jquery获取select,option所有的value和text的实例
2017/03/06 Javascript
简单实现jquery隔行变色
2017/11/09 jQuery
JS中用EL表达式获取上下文参数值的方法
2018/03/28 Javascript
Angular4 组件通讯方法大全(推荐)
2018/07/12 Javascript
ES2020 新特性(种草)
2020/01/12 Javascript
[54:54]Newbee vs Serenity 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/18 DOTA
在Python的Django框架中生成CSV文件的方法
2015/07/22 Python
Python使用time模块实现指定时间触发器示例
2017/05/18 Python
Django Web开发中django-debug-toolbar的配置以及使用
2018/05/06 Python
django配置连接数据库及原生sql语句的使用方法
2019/03/03 Python
详解Python3 中的字符串格式化语法
2020/01/15 Python
Python PyQt5模块实现窗口GUI界面代码实例
2020/05/12 Python
详解三种方式实现平滑滚动页面到顶部的功能
2019/04/23 HTML / CSS
Why we need EJB
2016/10/20 面试题
职业生涯规划书范文
2014/03/10 职场文书
机械设计及其自动化专业求职信
2014/06/09 职场文书
学校端午节活动方案
2014/08/23 职场文书
软环境建设心得体会
2014/09/09 职场文书
2014年教师节座谈会发言稿
2014/09/10 职场文书
2015年化妆品销售工作总结
2015/05/11 职场文书
win11如何查看端口是否被占用? Win11查看端口是否占用的技巧
2022/04/05 数码科技
海康机器人重磅发布全新算法开发平台VM4.2
2022/04/21 数码科技
Django框架中视图的用法
2022/06/10 Python