使用 honeystone/context 构建多租户应用程序

2024-08-11 0 492

不要与 Laravel 的新上下文库混淆,该包可用于构建多上下文多租户应用程序。大多数多租户库本质上都有一个“租户”上下文,因此如果您需要多个上下文,事情可能会变得有点麻烦。这个新包解决了这个问题。

让我们看一个例子好吗?

示例项目

对于我们的示例应用程序,我们将拥有一个组织成团队的全球用户群,每个团队将有多个项目。这是许多软件服务应用程序中相当常见的结构。

对于多租户应用程序来说,每个用户群都存在于一个租户上下文中并不罕见,但对于我们的示例应用程序,我们希望用户能够加入多个团队,所以它是全局用户群。
全球用户群与租户用户群图

使用 honeystone/context 构建多租户应用程序

作为 saas,团队很可能是计费实体(即席位),并且某些团队成员将被授予管理团队的权限。不过,我不会在此示例中深入探讨这些实现细节,但希望它提供一些额外的上下文。

安装

为了保持这篇文章简洁,我不会解释如何启动你的 laravel 项目。已经有许多更好的资源可用,尤其是官方文档。我们假设您已经有一个 laravel 项目,其中包含用户、团队和项目模型,并且您已准备好开始实现我们的上下文包。

安装很简单 作曲家推荐:

1

composer install honeystone/context

这个库有一个方便的函数 context(),从 laravel 11 开始,它与 laravel 自己的 context 函数发生冲突。这其实并不是一个问题。您可以导入我们的函数:

1

use function honestone\context\context;

或者直接使用 laravel 的依赖注入容器。在这篇文章中,我将假设您已导入该函数并相应地使用它。

型号

让我们从配置我们的团队模型开始:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

<?php declare(strict_types=1);

namespace app\models;

use illuminate\database\eloquent\model;

use illuminate\database\eloquent\relations\belongstomany;

use illuminate\database\eloquent\relations\hasmany;

class team extends model

{

    protected $fillable = ['name'];

    public function members(): belongstomany

    {

        return $this->belongstomany(user::class);

    }

    public function projects(): hasmany

    {

        return $this-&gt;hasmany(project::class);

    }

}

团队有名称、成员和项目。在我们的应用程序中,只有团队成员才能访问该团队或其项目。

好吧,让我们看看我们的项目:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<?php declare(strict_types=1);

namespace app\models;

use illuminate\database\eloquent\model;

use illuminate\database\eloquent\relations\belongsto;

class project extends model

{

    protected $fillable = ['name'];

    public function team(): belongsto

    {

        return $this->belongsto(team::class);

    }

}

一个项目有一个名字并且属于一个团队。

确定上下文

当有人访问我们的应用程序时,我们需要确定他们在哪个团队和项目中工作。为了简单起见,我们用路由参数来处理这个问题。我们还假设只有经过身份验证的用户才能访问该应用程序。

既不是团队也不是项目上下文: app.mysaas.dev
仅团队上下文: app.mysaas.dev/my-team
团队和项目上下文: app.mysaas.dev/my-team/my-project

我们的路线将如下所示:

1

2

3

4

5

6

7

8

9

10

route::middleware('auth')-&gt;group(function () {

    route::get('/', dashboardcontroller::class);

    route::middleware(appcontextmiddleware::class)-&gt;group(function () {

        route::get('/{team}', teamcontroller::class);

        route::get('/{team}/{project}', projectcontroller::class);

    });

});

考虑到命名空间冲突的可能性,这是一种非常不灵活的方法,但它使示例保持简洁。在现实世界的应用程序中,您需要稍微不同地处理这个问题,也许是 anothersaas.dev/teams/my-team/projects/my-project 或 my-team.anothersas.dev/projects/my-project。

我们应该首先看看我们的appcontextmiddleware。该中间件初始化团队上下文以及项目上下文(如果设置):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

<?php declare(strict_types=1);

namespace app\HTTP\middleware;

use function honestone\context\context;

class teamcontextmiddleware

{

    public function handle(request $request, closure $next): mixed

    {

        //pull the team parameter from the route

        $teamid = $request->route('team');

        $request-&gt;route()-&gt;forgetparameter('team');

        $projectid = null;

        //if there's a project, pull that too

        if ($request-&gt;route()-&gt;hasparamater('project')) {

            $projectid = $request-&gt;route('project');

            $request-&gt;route()-&gt;forgetparameter('project');

        }

        //initialise the context

        context()-&gt;initialize(new appresolver($teamid, $projectid));

    }

}

首先,我们从路线中获取团队 id,然后忘记路线参数。一旦参数进入上下文,我们就不需要到达控制器。如果设置了项目 id,我们也会提取它。然后,我们使用 appresolver 传递团队 id 和项目 id(或 null)来初始化上下文:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

<?php declare(strict_types=1);

namespace app\context\resolvers;

use app\models\team;

use honeystone\context\contextresolver;

use honeystone\context\contracts\definescontext;

use function honestone\context\context;

class appresolver extends contextresolver

{

    public function __construct(

        private readonly int $teamid,

        private readonly ?int $projectid = null,

    ) {}

    public function define(definescontext $definition): void

    {

        $definition

            ->require('team', team::class)

            -&gt;accept('project', project::class);

    }

    public function resolveteam(): ?team

    {

        return team::with('members')-&gt;find($this-&gt;teamid);

    }

    public function resolveproject(): ?project

    {

        return $this-&gt;projectid ?: project::with('team')-&gt;find($this-&gt;projectid);

    }

    public function checkteam(definescontext $definition, team $team): bool

    {

        return $team-&gt;members-&gt;find(context()-&gt;auth()-&gt;getuser()) !== null;

    }

    public function checkproject(definescontext $definition, ?project $project): bool

    {

        return $project === null || $project-&gt;team-&gt;id === $this-&gt;teamid;

    }

    public function deserialize(array $data): self

    {

        return new static($data['team'], $data['project']);

    }

}

这里还有更多事情要做。

define() 方法负责定义正在解析的上下文。团队是必需的并且必须是 team 模型,并且项目被接受(即可选)并且必须是 project 模型(或 null)。

resolveteam() 将在初始化时在内部调用。它返回 team 或 null。如果出现空响应,contextinitializer 将抛出 couldnotresolverequiredcontextexception。

resolveproject() 也将在初始化时在内部调用。它返回项目或 null。在这种情况下,空响应不会导致异常,因为定义不需要该项目。

解析团队和项目后,contextinitializer 将调用可选的 checkteam() 和 checkproject() 方法。这些方法执行完整性检查。对于 checkteam(),我们确保经过身份验证的用户是团队的成员,对于 checkproject(),我们检查项目是否属于团队。

最后,每个解析器都需要一个 deserialization() 方法。此方法用于恢复序列化上下文。最值得注意的是,当在排队作业中使用上下文时,会发生这种情况。

现在我们的应用程序上下文已经设置好了,我们应该使用它。

访问上下文

像往常一样,我们会保持简单,尽管有点做作。查看团队时,我们希望看到项目列表。我们可以构建我们的 teamcontroller 来处理这样的需求:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

<?php declare(strict_types=1);

namespace app\http\controllers;

use illuminate\view\view;

use function compact;

use function honestone\context\context;

use function view;

class teamcontroller

{

    public function __invoke(request $request): view

    {

        $projects = context('team')->projects;

        return view('team', compact('projects'));

    }

}

足够简单。属于当前团队上下文的项目将传递到我们的视图。想象一下,我们现在需要查询项目以获得更专业的视图。我们可以这样做:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

<?php declare(strict_types=1);

namespace app\http\controllers;

use illuminate\view\view;

use function compact;

use function honestone\context\context;

use function view;

class projectquerycontroller

{

    public function __invoke(request $request, string $query): view

    {

        $projects = project::Where('team_id', context('team')->id)

            -&gt;where('name', 'like', "%$query%")

            -&gt;get();

        return view('queried-projects', compact('projects'));

    }

}

现在变得有点麻烦,而且很容易意外地忘记按团队“确定查询范围”。我们可以使用项目模型上的 belongstocontext 特征来解决这个问题:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

<?php declare(strict_types=1);

namespace app\models;

use honeystone\context\models\concerns\belongstocontext;

use illuminate\database\eloquent\model;

use illuminate\database\eloquent\relations\belongsto;

class project extends model

{

    use belongstocontext;

    protected static array $context = ['team'];

    protected $fillable = ['name'];

    public function team(): belongsto

    {

        return $this->belongsto(team::class);

    }

}

所有项目查询现在都将由团队上下文获取,并且当前的团队模型将自动注入到新的项目模型中。

让我们简化该控制器:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<?php declare(strict_types=1);

namespace App\Http\Controllers;

use Illuminate\View\View;

use function compact;

use function view;

class ProjectQueryController

{

    public function __invoke(Request $request, string $query): View

    {

        $projects = Project::where('name', 'like', "%$query%")->get();

        return view('queried-projects', compact('projects'));

    }

}

这就是大家

从这里开始,您只需构建您的应用程序即可。上下文很容易获得,您的查询是有范围的,排队的作业将自动访问调度它们的相同上下文。

并非所有与上下文相关的问题都得到解决。您可能想要创建一些验证宏来为您的验证规则提供一些上下文,并且不要忘记手动查询不会自动应用上下文。

如果您计划在下一个项目中使用此软件包,我们很乐意听取您的意见。随时欢迎反馈和贡献。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

免责声明
1. 本站所有资源来源于用户上传和网络等,如有侵权请邮件联系本站整改team@lcwl.fun!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系本站工作人员处理!
6. 本站资源售价或VIP只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 因人力时间成本问题,部分源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别!
9.本站所有源码资源都是经过本站工作人员人工亲测可搭建的,保证每个源码都可以正常搭建,但不保证源码内功能都完全可用,源码属于可复制的产品,无任何理由退款!

网站搭建学习网 PHP 使用 honeystone/context 构建多租户应用程序 https://www.xuezuoweb.com/10688.html

常见问题
  • 本站所有的源码都是经过平台人工部署搭建测试过可用的
查看详情
  • 购买源码资源时购买了带主机的套餐是指可以享受源码和所选套餐型号的主机两个产品,在本站套餐里开通主机可享优惠,最高免费使用主机
查看详情

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务

Fa快捷助手
手机编程软件开发

在手机上用手点一点就能轻松做软件

去做软件
链未云主机
免备案香港云主机

开通主机就送域名的免备案香港云主机

去使用
链未云服务器
免备案香港云服务器

支持售后、超低价、稳定的免备案香港云服务器

去使用