PHPackages                             janfish/swoft-saas - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. janfish/swoft-saas

ActiveProject[Framework](/categories/framework)

janfish/swoft-saas
==================

Swoft SAAS Framework

v1.4.4(3y ago)04Apache-2.0PHP

Since Apr 19Pushed 3y ago1 watchersCompare

[ Source](https://github.com/zeng444/swoft-sass)[ Packagist](https://packagist.org/packages/janfish/swoft-saas)[ RSS](/packages/janfish-swoft-saas/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependenciesVersions (6)Used By (0)

业务网关
----

[](#业务网关)

### 配置说明

[](#配置说明)

- .env

```
SERVER_DOMAIN 申明服务对应数据库配置的服务器，用于load下属的服务给网关

```

### 系统租客识别

[](#系统租客识别)

- 系统通过App\\Rpc\\Client\\Contract\\Balancer，载入了业务网关下可用的服务，配置来源于lightning\_center.service中的服务信息
- 系统通过App\\Rpc\\Client\\Contract\\Extender，定制了JSON RPC协议中携带的信息，定制了tenantId、db、appId、appSecret参数发送到服务中去，实现了RPC权限认证和租户的服务数据库定位

字段类型说明tenantIdint租户Iddbstring租户所属数据库appIdstring服务权限验证IDappSecretstring服务权限验证KEY- HTTP反向代理的配置

nginx.conf需要传递IP和SCHEME信息

```
server {
    listen 80;
    server_name cat.cn;
    location / {
        proxy_pass http://swoft-cat:18306;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header REQUEST_SCHEME $scheme;
    }
}

```

### 访问租客服务

[](#访问租客服务)

- 说明

租客凭证决定如何访问服务，但特殊资源需要指定访问服务，如资源类，像系统菜单批量更新，可以采用 App\\Common\\Remote\\ServiceRpc实现，特别注意的是只有当当前不存在凭证时才能调用此方法

- 方法

```
(\Swoft::getBean(ServiceRpc::class))->handle(Closure $callback, string $serviceCode = '', string $dbName = ''): void
```

- 参数

参数类型说明callbackClosure回调函数，内部的服务调用会自动向本网关所有服务发送serviceCodestring指定向某台代号的服务调用，默认所有服务dbNamestring指定向某台代号下的指定服务的指定数据库发送，默认所有数据库- 示例

```
/**
* @Reference("biz.pool")
* @var AclRouteInterface
*/
protected $aclRouteService;

/**
* 批量请求指定服务的aclRouteService::build方法
* Author:Robert
*
* @param string $serviceCode
* @param string $database
* @return bool
*/
public function test(string $serviceCode, string $database): bool
{
    $aclRouteService = $this->aclRouteService;
        $result = (\Swoft::getBean(ServiceRpc::class))->handle(static function (string $serviceCode, string $dbName) use ($aclRouteService) {
        return $aclRouteService->build($data)
    }, $serviceCode, $database);
}
```

### 访问租客服务(TenantId)

[](#访问租客服务tenantid)

- 说明

租客凭证决定如何访问服务，但特殊资源需要通过租客ID，访问对应的服务，如资源类，像系统菜单批量更新，可以采用 App\\Common\\Remote\\TenantRpc实现，特别注意的是只有当前不存在凭证时才能调用此方法

- 方法

```
(\Swoft::getBean(TenantRpc::class))->handle(Closure $callback, int $tenantId = null): void
```

- 参数

参数类型说明callbackClosure回调函数，内部的服务调用会自动向本网关所有服务发送tenantIdstring指定租客ID的调用，默认所有租客- 示例

```
//接受微信通知并回调到指定租客服务
$tenantId = $request->intut("tenantId");

\Swoft::getBean(TenantRpc::class)->handle(static function ($tenantId) use ($paymentLogic, $paymentHeader, $raw) {

$paymentLogic = \Swoft::getBean(PaymentLogic::class);
$paymentLogic->notify($raw, $paymentHeader);

}, $tenantId);
```

### 系统快捷方法

[](#系统快捷方法)

#### 获取当前登录用户

[](#获取当前登录用户)

```
currentUserId(): int
```

#### 获取当前登录用户分组

[](#获取当前登录用户分组)

```
currentGroupId(): int
```

#### 获取当前租户

[](#获取当前租户)

```
currentTenantId(): int
```

#### 获取当前用户会话

[](#获取当前用户会话)

```
session()
```

#### 获取当前用户扩展信息

[](#获取当前用户扩展信息)

```
session()->->getExtendedData(): array
```

或

```
sessionExtData(): array
```

#### 判断当前用户是否为超级管理员

[](#判断当前用户是否为超级管理员)

```
currentIsSuper(): bool
```

#### 判断当前用户数据读取权限

[](#判断当前用户数据读取权限)

```
currentReader(): string
```

权限名称权限说明PERSONAL自己的数据GROUP小组成员数据FULL所有数据服务端
---

[](#服务端)

### 数据库租户策略

[](#数据库租户策略)

- 通过服务拦截器，获取了从应用层底层发送的租客条件（tenantId）
- 通过App\\Common\\Db\\DbSelector对象，实现了不同租客（tenantId）,切换各自数据库的过程
- 通过App\\Common\\Db\\MySqlConnection的Mysql链接对象，对ORM和DAO等方式生产的SQL进行了解析，并自动应用了租客条件（tenantId）的SQL CURD操作补足
- 实际编写代码时，数据库CURD操作时，不需要编写tenantId相关条件，如果依然编写了tenantId条件，则会以你编写的为准，但需要注意的是inner查询，涉及到多个表，注入查询的是主表的tenantId作为查询条件
- 公共资源表，即tenantId始终为0的表，编写SQL时，操作CURD时候，需要设置tenantId使用明文作为条件，值取使用0，但已知问题：使用条件找到模型修改再保存，会自动注入tenantId查询条件，导致保存失败，这个是由于ORM保存时生成条件是以ID作为条件，是因为规则未指定tenantId时，强行注入tenantId造成的，解决方案是直接where条件保存，不要先查询再保存，或者使用“停止注入的闭包函数”手动控制

```
### 问题
$model = Menu::where([
    ['id', 3],
    ['tenantId', 0]
]);
/** @var Menu $menu */
$menu = $model->first();
$menu->setSort(100);
$menu->save();
//生成SQL错误强制注入了当前的tenantId在保存条件时
//UPDATE `menu` SET `sort` = 100, `updatedAt` = '2022-04-19 12:02:54' WHERE `id` = 3 AND tenantId = 1

### 改写为下面方式更新来解决

Menu::where([
    ['id', 3],
    ['tenantId',0]
])->update(['sort'=> 100]);
```

#### 停止注入的闭包函数

[](#停止注入的闭包函数)

- 对于tenantId始终等于0的数据表使用

```
$result = \App\Common\Db\MySqlConnection::noTenant( static function ( $currentTenantId ) {
$model = Menu::where([
    ['id', 3],
    ['tenantId', 0]
]);
/** @var Menu $menu */
$menu = $model->first();
$menu->setSort(100);
return $menu->save();
});
```

### 系统快捷方法

[](#系统快捷方法-1)

#### 获取当前请求服务的租户id

[](#获取当前请求服务的租户id)

```
currentTenantId(): int
```

#### 获取当前运行得服务代号

[](#获取当前运行得服务代号)

```
currentServiceCode(): string
```

#### 获取当前数据库

[](#获取当前数据库)

```
currentDB(): string
```

### 异步调用

[](#异步调用)

- 单个

```
#任务投递
\App\Common\Async\Task::async(OrderLogic::class, 'add', [$line])
```

```
#协程任务投递
\App\Common\Async\Task::co(OrderLogic::class, 'add', [$line]);
```

- 持久化异步调用

```
#启动进程池
php bin/swoft process:start
```

```
#持久化调用
App\Common\Async\Client::async(OrderLogic::class, 'add', [$line]);
```

- 批量

```
#协程任务投递
\App\Common\Async\Task::cos([
[OrderLogic::class, 'add', [$line]]
]);
```

### 同步调用

[](#同步调用)

```
\App\Common\Caller\Client::call($className, $methodName, $params)
```

### 对象存储

[](#对象存储)

- 创建对象

```
\Swoft::getBean(App\Common\Oss\Client::class)-->upload(['excel202135/2499618269.png'], 'excel', 'WEEK', true)；
```

- 获取对象

```
\Swoft::getBean(App\Common\Oss\Client::class)->pull('excel202135/2499618269.png',alias('@runtime/caches/1.png'));
```

- 删除对象

```
\Swoft::getBean(App\Common\Oss\Client::class)->delete(["excel/202135/1329041768.png"]);
```

### Excel读取（自适应xlsx、csv等格式）

[](#excel读取自适应xlsxcsv等格式)

> 支持格式 'Xlsx', 'Xls', 'Xml', 'Ods', 'Ods', 'Slk', 'Gnumeric', 'Html', 'Csv'

```
use App\Common\Excel\Reader;

/** @var Reader $excelReader */
$excelReader = \Swoft::getBean(Reader::class);
$excelReader->loadFile($dist);
$excelReader->setSheet(0);
$excelReader->setStartRow(2);
$excelReader->setRule('F',Reader::DATETIME_ROLE);
$excelReader->setRule('H',Reader::STRING_ROLE);
$excelReader->setRules([
    'F' => Reader::DATETIME_ROLE,
    'H' => Reader::STRING_ROLE
]);
```

```
$excelReader->forEach(static function ($line, $isEnd, $row) {
    print_r([
        $row, $line
    ]);
});
```

```
$excelReader->chunk(static function ($rows, $isEnd) {
    print_r(rows);
},20);
```

### Excel写入（自适应xlsx、csv等格式）

[](#excel写入自适应xlsxcsv等格式)

> 支持格式 'Xlsx', 'Csv', 'Xls', 'Ods', 'Html', 'Tcpdf', 'Dompdf', 'Mpdf'

```
$writer = new \App\Common\Excel\Writer();
$writer->setTitle('批次到处任务');
$writer->setHeader(['序号', '车牌号', '车主姓名', '联系电话', '询价状态', '失败原因']);
foreach ($rows as $row) {
    $writer->writeRow($row);
}
$dist = 'runtime/20210930.xlsx';
$writer->save($dist, 'Xlsx');
```

- 通过setRule强制转换列值属性，解决时间读取等问题

Rule规则说明Reader::DATETIME\_ROLE转换为日期时间Reader::DATE\_ROLE转换为日期Reader::TIME\_ROLE转换为时间Reader::INTEGER\_ROLE转换为整形Reader::STRING\_ROLE转换为字符串Reader::DOUBLE\_ROLE转换浮点### 大内存运行

[](#大内存运行)

- 临时调整内存运行，完成后恢复默认设置

```
App\Common\Memory\Handle::run(static function ($originalMemory, $memory) {
//大内存操作
},'200M');
```

###  Health Score

23

—

LowBetter than 27% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity3

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~12 days

Total

5

Last Release

1432d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/60a62fd9f299f25558ac4a898ae3833bd7185ba4706bb167a8cabf05a3d96955?d=identicon)[Robert Zeng](/maintainers/Robert%20Zeng)

---

Top Contributors

[![zeng444](https://avatars.githubusercontent.com/u/941266?v=4)](https://github.com/zeng444 "zeng444 (52 commits)")

---

Tags

phpsaasswoft

### Embed Badge

![Health badge](/badges/janfish-swoft-saas/health.svg)

```
[![Health](https://phpackages.com/badges/janfish-swoft-saas/health.svg)](https://phpackages.com/packages/janfish-swoft-saas)
```

###  Alternatives

[swoft/framework

swoft framework component

64181.4k85](/packages/swoft-framework)[pestphp/pest-plugin-stressless

Stressless plugin for Pest

67792.6k16](/packages/pestphp-pest-plugin-stressless)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
