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

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

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

概念抽象

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

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

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

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

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

怎么展示

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

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

我最近正在开发 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');   
   
$activity = Activity::all()->last();
$activity->subject; //returns an instance of an eloquent model
$activity->causer; //returns an instance of your user model
$activity->getExtraProperty('customProperty'); //returns 'customValue'
$activity->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,用户动态模块的开发就分享到这里,如果你有更高级的实现欢迎随时交流。

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

本文代码部分在手机上的浏览效果可能比较差,所以建议你使用 PC 浏览

原文发布于微信公众号 - 假装我会写代码(bugszoo)

原文发表时间:2017-09-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

利用QRLJacking工具获取Whatsapp帐号权限

这篇文章将会教你如何用 QRLJacking 去获取别人 Whatsapp 帐号的权限,这种攻击是交互式,需要受害者去扫描你搭建好的钓鱼网站里面的恶意二维码,或...

22020
来自专栏DannyHoo的专栏

得到AppStore中App的下载链接

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/details/...

73320
来自专栏大数据和云计算技术

操作系统底层技术——CPU亲和性

image.png 头图是加拿大lake simcoe自然风光,非常漂亮,基本没有中国游客,适合深度游。 这是操作系统底层技术第二篇,前一篇是《Codegen技...

33190
来自专栏互联网开发者交流社区

SEO-搜索引擎高级搜索指令

18450
来自专栏Android机动车

安卓优化之apk瘦身(27.7M--&gt;17.5M)

apk瘦身作为优化的一部分,它的大小决定安装的时间与占用的内存,进行针对性的瘦身也能够提高用户体验,下面就看我怎样将一个27.7M的安装包减肥到17.5M,足足...

12540
来自专栏刘望舒

Android系统架构与系统源码目录

前言 技术博客终于可以恢复正常的更新速度了,原因是我编写的进阶书籍的初稿已经完成,窃以为它将会是Android应用书籍中最有深度的一本,可以说是《Android...

42680
来自专栏实用工具入门教程

如何部署 NTP 服务器

NTP 全称为 Network Time Protocol ,中文翻译为网络时间协议,1985年就已经被提出,旨在缩短互联网上所有计算机设备与 UTC 的时间差...

57210
来自专栏web开发

javascript设计模式——发布订阅模式

前面的话   发布—订阅模式又叫观察者模式,它定义对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在javascript开...

95060
来自专栏厦门SEO

如何获得微信/QQ小绿标?腾讯云网址安全认证教程!

打开微信/QQ,聊天框发送已经完成URL安全认证的网址,看看小绿标是否生效即可~

2.3K10
来自专栏.Net移动开发

4.6预告先导篇——你们关心的几个问题:关于页、文档、内网推送等

非常感谢大家对Smobiler的支持,从4.6版本的建议征集中,整理了几个大家关注得比较多的问题,在此单独列出答复。

14220

扫码关注云+社区

领取腾讯云代金券