帮助文档
{{userInfo.nickname}}
用户设置 退出登录

{{wikiTitle}}

核心依赖库crmeb目录说明

核心依赖库crmeb目录说明

概述

crmeb 目录是CRMEB多店系统的核心依赖库,包含了系统运行所需的基础类、服务封装、工具函数、接口定义等核心代码。这些代码与业务逻辑解耦,提供了可复用的底层功能支持。

目录结构

crmeb/
├── basic/              # 基础类目录
│   ├── BaseAuth.php           # 认证基类
│   ├── BaseController.php     # 控制器基类
│   ├── BaseDelivery.php       # 配送基类
│   ├── BaseErp.php            # ERP基类
│   ├── BaseExpress.php        # 快递基类
│   ├── BaseJobs.php           # 队列任务基类
│   ├── BaseManager.php        # 管理器基类
│   ├── BaseMessage.php        # 消息基类
│   ├── BaseModel.php          # 模型基类
│   ├── BasePay.php            # 支付基类
│   ├── BasePrinter.php        # 打印机基类
│   ├── BaseProduct.php        # 商品基类
│   ├── BaseSms.php            # 短信基类
│   ├── BaseSmss.php           # 短信服务基类
│   ├── BaseStorage.php        # 存储基类
│   └── BaseUpload.php         # 上传基类
├── command/            # 命令行工具
│   └── ...
├── exceptions/         # 异常类目录
│   ├── AdminException.php     # 后台异常
│   ├── ApiException.php       # API异常
│   ├── AuthException.php      # 认证异常
│   ├── PayException.php       # 支付异常
│   ├── SmsException.php       # 短信异常
│   ├── UploadException.php    # 上传异常
│   └── ...
├── form/               # 表单构建器
│   ├── BaseComponent.php      # 组件基类
│   ├── Build.php              # 表单构建类
│   ├── CommonRule.php         # 通用规则
│   ├── components/            # 表单组件
│   └── validate/              # 表单验证
├── interfaces/         # 接口定义
│   ├── HandlerInterface.php   # 处理器接口
│   ├── JobInterface.php       # 队列任务接口
│   ├── ListenerInterface.php  # 监听器接口
│   ├── MiddlewareInterface.php # 中间件接口
│   └── ProviderInterface.php  # 服务提供者接口
├── listeners/          # 系统监听器
│   └── ...
├── services/           # 服务类目录
│   ├── AccessTokenServeService.php  # Token服务
│   ├── AliPayService.php            # 支付宝服务
│   ├── CacheService.php             # 缓存服务
│   ├── DownloadImageService.php     # 图片下载服务
│   ├── FileService.php              # 文件服务
│   ├── HttpService.php              # HTTP请求服务
│   ├── LockService.php              # 锁服务
│   ├── MysqlBackupService.php       # 数据库备份服务
│   ├── QrcodeService.php            # 二维码服务
│   ├── SpreadsheetExcelService.php  # Excel服务
│   ├── SystemConfigService.php      # 系统配置服务
│   ├── UploadService.php            # 上传服务
│   ├── delivery/                    # 配送服务
│   ├── express/                     # 快递服务
│   ├── printer/                     # 打印服务
│   ├── sms/                         # 短信服务
│   ├── upload/                      # 上传驱动
│   └── wechat/                      # 微信服务
├── traits/             # Trait特性
│   ├── ErrorTrait.php         # 错误处理特性
│   ├── JwtAuthModelTrait.php  # JWT模型特性
│   ├── ModelTrait.php         # 模型特性
│   ├── QueueTrait.php         # 队列特性
│   ├── SearchDaoTrait.php     # 搜索Dao特性
│   ├── ServicesTrait.php      # 服务特性
│   └── ...
└── utils/              # 工具类目录
    ├── ApiErrorCode.php       # API错误码
    ├── Arr.php                # 数组工具
    ├── Canvas.php             # 画布工具
    ├── Captcha.php            # 验证码
    ├── Cron.php               # 定时任务
    ├── Hook.php               # 钩子
    ├── Json.php               # JSON工具
    ├── JwtAuth.php            # JWT认证
    ├── QRcode.php             # 二维码生成
    ├── Queue.php              # 队列工具
    ├── Start.php              # 启动工具
    └── Tag.php                # 标签工具

一、基础类 (basic/)

1.1 BaseController - 控制器基类

所有控制器的基类,提供通用功能。

<?php
namespace crmeb\basic;

use think\App;

abstract class BaseController
{
    /**
     * 应用实例
     * @var App
     */
    protected $app;

    /**
     * Request实例
     * @var \think\Request
     */
    protected $request;

    /**
     * 构造方法
     */
    public function __construct(App $app)
    {
        $this->app = $app;
        $this->request = $this->app->request;
        $this->initialize();
    }

    /**
     * 初始化
     */
    protected function initialize()
    {
    }
}

1.2 BaseJobs - 队列任务基类

所有队列任务的基类:

<?php
namespace crmeb\basic;

use crmeb\interfaces\JobInterface;
use think\queue\Job;

/**
 * 消息队列基类
 */
class BaseJobs implements JobInterface
{
    /**
     * 运行消息队列
     * @param Job $job
     * @param array $data
     */
    public function fire(Job $job, $data): void
    {
        try {
            $action = $data['do'] ?? 'doJob';      // 任务名
            $infoData = $data['data'] ?? [];       // 执行数据
            $errorCount = $data['errorCount'] ?? 0; // 最大错误次数
            $this->runJob($action, $job, $infoData, $errorCount);
        } catch (\Throwable $e) {
            $job->delete();
        }
    }

    /**
     * 执行队列
     */
    protected function runJob(string $action, Job $job, array $infoData, int $errorCount = 3)
    {
        $action = method_exists($this, $action) ? $action : 'handle';

        if (!method_exists($this, $action)) {
            $job->delete();
            return;
        }

        if ($this->{$action}(...$infoData)) {
            // 执行成功,删除任务
            $job->delete();
        } else {
            if ($job->attempts() >= $errorCount && $errorCount) {
                // 超过重试次数,删除任务
                $job->delete();
            } else {
                // 重新放入队列
                $job->release();
            }
        }
    }
}

创建队列任务示例

<?php
namespace app\jobs\order;

use crmeb\basic\BaseJobs;

/**
 * 订单处理队列任务
 */
class OrderJob extends BaseJobs
{
    /**
     * 处理订单
     * @param int $orderId
     * @return bool
     */
    public function doJob(int $orderId): bool
    {
        try {
            // 处理订单逻辑
            $orderService = app()->make(\app\services\order\StoreOrderServices::class);
            $orderService->processOrder($orderId);
            return true;
        } catch (\Exception $e) {
            \think\facade\Log::error('订单处理失败:' . $e->getMessage());
            return false;
        }
    }

    /**
     * 发送订单通知
     */
    public function sendNotify(int $orderId, int $uid): bool
    {
        // 发送通知逻辑
        return true;
    }
}

1.3 BaseUpload - 上传基类

文件上传的基础类:

<?php
namespace crmeb\basic;

abstract class BaseUpload
{
    /**
     * 上传文件
     * @param string $file 文件路径
     * @param bool $isStream 是否为流
     * @param string|null $fileContent 文件内容
     * @return array
     */
    abstract public function move(string $file, bool $isStream = false, ?string $fileContent = null): array;

    /**
     * 删除文件
     * @param string $filePath 文件路径
     * @return bool
     */
    abstract public function delete(string $filePath): bool;

    /**
     * 获取上传信息
     * @return array
     */
    abstract public function getUploadInfo(): array;
}

二、服务类 (services/)

2.1 CacheService - 缓存服务

统一的缓存操作服务:

<?php
namespace crmeb\services;

use think\facade\Cache as CacheStatic;

/**
 * 缓存服务类
 * @mixin \Redis
 */
class CacheService
{
    protected static $globalCacheName = '_cached_1515146130';

    /**
     * 判断缓存是否存在
     */
    public static function has(string $name): bool
    {
        return CacheStatic::has($name);
    }

    /**
     * 写入缓存
     * @param string $name 缓存名称
     * @param mixed $value 缓存值
     * @param int $expire 过期时间(秒)
     */
    public static function set(string $name, $value, int $expire = null): bool
    {
        return self::handler()->set($name, $value, $expire);
    }

    /**
     * 读取缓存,不存在则执行回调并缓存
     */
    public static function get(string $name, $default = false, int $expire = null)
    {
        return self::handler()->remember($name, $default, $expire);
    }

    /**
     * 删除缓存
     */
    public static function delete(string $name)
    {
        return CacheStatic::delete($name);
    }

    /**
     * 清空缓存池
     */
    public static function clear()
    {
        return self::handler()->clear();
    }

    /**
     * Redis句柄
     */
    public static function redisHandler(string $type = null)
    {
        if ($type) {
            return CacheStatic::store('redis')->tag($type);
        }
        return CacheStatic::store('redis');
    }

    /**
     * 设置库存队列(Redis)
     */
    public static function setStock(string $unique, int $number, int $type = 1, bool $isPush = true)
    {
        // 使用Redis List实现库存队列
        // ...
    }

    /**
     * 弹出库存
     */
    public static function popStock(string $unique, int $number, int $type = 1)
    {
        // 从Redis List弹出库存
        // ...
    }

    /**
     * 分布式锁
     */
    public static function setMutex(string $key, int $timeout = 10)
    {
        $curTime = time();
        $readMutexKey = "redis:mutex:{$key}";
        $mutexRes = self::redisHandler()->handler()->setnx($readMutexKey, $curTime + $timeout);

        if ($mutexRes) {
            return true;
        }

        // 检查锁是否过期
        $time = self::redisHandler()->handler()->get($readMutexKey);
        if ($curTime > $time) {
            self::redisHandler()->handler()->del($readMutexKey);
            return self::redisHandler()->handler()->setnx($readMutexKey, $curTime + $timeout);
        }

        return false;
    }

    /**
     * 释放锁
     */
    public static function delMutex(string $key)
    {
        $readMutexKey = "redis:mutex:{$key}";
        self::redisHandler()->handler()->del($readMutexKey);
    }
}

使用示例

// 基本缓存操作
CacheService::set('user_info_1', $userData, 3600);
$userData = CacheService::get('user_info_1');
CacheService::delete('user_info_1');

// 使用回调获取缓存
$config = CacheService::get('site_config', function() {
    return SystemConfigService::more(['site_name', 'site_logo']);
}, 86400);

// 使用分布式锁
if (CacheService::setMutex('order_pay_' . $orderId, 30)) {
    try {
        // 处理订单支付
        $this->processPayment($orderId);
    } finally {
        CacheService::delMutex('order_pay_' . $orderId);
    }
}

// 库存操作(秒杀场景)
CacheService::setStock($productUnique, 100, 1); // 设置100个库存
if (CacheService::checkStock($productUnique, 1, 1)) {
    CacheService::popStock($productUnique, 1, 1); // 扣减1个库存
}

2.2 SystemConfigService - 系统配置服务

读取系统配置:

<?php
namespace crmeb\services;

class SystemConfigService
{
    /**
     * 获取单个配置
     * @param string $key 配置键名
     * @param mixed $default 默认值
     * @param bool $isCaChe 是否使用缓存
     */
    public static function get(string $key, $default = '', bool $isCaChe = false)
    {
        // 实现逻辑
    }

    /**
     * 获取多个配置
     * @param array $keys 配置键名数组
     */
    public static function more(array $keys, int $storeId = 0)
    {
        // 实现逻辑
    }
}

使用示例

// 获取单个配置
$siteName = SystemConfigService::get('site_name', 'CRMEB');

// 获取多个配置
$config = SystemConfigService::more(['site_name', 'site_logo', 'site_url']);

// 获取门店配置
$storeConfig = SystemConfigService::more(['store_name'], $storeId);

2.3 微信服务 (services/wechat/)

Payment - 微信支付服务

<?php
namespace crmeb\services\wechat;

class Payment extends BaseApplication
{
    /**
     * 创建订单
     * @param string $orderId 订单号
     * @param string $totalFee 金额
     * @param string $body 商品描述
     * @param string $notifyUrl 回调地址
     * @param string $tradeType 交易类型
     */
    public function create(string $orderId, string $totalFee, string $body, string $notifyUrl, string $tradeType = 'JSAPI')
    {
        // 创建微信支付订单
    }

    /**
     * 退款
     */
    public function refund(string $orderId, string $refundNo, float $totalFee, float $refundFee)
    {
        // 处理退款
    }

    /**
     * 查询订单
     */
    public function query(string $orderId)
    {
        // 查询订单状态
    }
}

OfficialAccount - 公众号服务

<?php
namespace crmeb\services\wechat;

class OfficialAccount extends BaseApplication
{
    /**
     * 获取JSSDK配置
     */
    public function jssdk(array $apis = [])
    {
        // 返回JSSDK配置
    }

    /**
     * 发送模板消息
     */
    public function sendTemplate(string $openid, string $templateId, array $data, string $url = '')
    {
        // 发送模板消息
    }

    /**
     * 获取用户信息
     */
    public function getUserInfo(string $openid)
    {
        // 获取用户信息
    }

    /**
     * 创建二维码
     */
    public function qrcode(string $scene, int $expireSeconds = 604800)
    {
        // 创建带参数二维码
    }
}

使用示例

use crmeb\services\wechat\Payment;
use crmeb\services\wechat\OfficialAccount;

// 微信支付
$payment = app()->make(Payment::class);
$result = $payment->create($orderId, $amount, '商品购买', $notifyUrl, 'JSAPI');

// 公众号服务
$official = app()->make(OfficialAccount::class);

// 发送模板消息
$official->sendTemplate($openid, $templateId, [
    'first' => ['value' => '订单支付成功'],
    'keyword1' => ['value' => $orderNo],
    'keyword2' => ['value' => $amount],
    'remark' => ['value' => '感谢您的购买']
], $detailUrl);

// 获取JSSDK配置
$jssdkConfig = $official->jssdk(['chooseWXPay', 'scanQRCode']);

2.4 UploadService - 上传服务

<?php
namespace crmeb\services;

class UploadService
{
    /**
     * 获取上传实例
     * @param int $type 上传类型 1=本地 2=七牛 3=OSS 4=COS
     */
    public static function init(int $type = null)
    {
        // 返回对应的上传驱动实例
    }
}

使用示例

// 使用系统配置的上传方式
$upload = UploadService::init();
$result = $upload->move('image.jpg');

// 指定本地上传
$localUpload = UploadService::init(1);

// 指定七牛云上传
$qiniuUpload = UploadService::init(2);

// 指定阿里OSS上传
$ossUpload = UploadService::init(3);

// 指定腾讯COS上传
$cosUpload = UploadService::init(4);

2.5 HttpService - HTTP请求服务

<?php
namespace crmeb\services;

class HttpService
{
    /**
     * GET请求
     */
    public static function getRequest(string $url, array $data = [], array $header = [])
    {
        // 发送GET请求
    }

    /**
     * POST请求
     */
    public static function postRequest(string $url, $data = [], array $header = [])
    {
        // 发送POST请求
    }

    /**
     * CURL请求
     */
    public static function request(string $url, string $method = 'GET', $data = null, array $header = [], int $timeout = 30)
    {
        // 通用CURL请求
    }
}

三、工具类 (utils/)

3.1 JwtAuth - JWT认证工具

<?php
namespace crmeb\utils;

use Firebase\JWT\JWT;

class JwtAuth
{
    /**
     * 生成Token
     * @param int $id 用户ID
     * @param string $type 用户类型
     * @param array $params 额外参数
     */
    public function getToken(int $id, string $type, array $params = []): array
    {
        $host = app()->request->host();
        $time = time();
        $exp_time = strtotime('+ 7day');

        // APP端延长有效期
        if (app()->request->isApp()) {
            $exp_time = strtotime('+ 30day');
        }

        $params += [
            'iss' => $host,      // 签发者
            'aud' => $host,      // 接收者
            'iat' => $time,      // 签发时间
            'nbf' => $time,      // 生效时间
            'exp' => $exp_time,  // 过期时间
        ];

        $params['jti'] = compact('id', 'type');
        $token = JWT::encode($params, env('app.app_key'));

        return compact('token', 'params');
    }

    /**
     * 解析Token
     */
    public function parseToken(string $jwt): array
    {
        [$headb64, $bodyb64, $cryptob64] = explode('.', $jwt);
        $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64));
        return [$payload->jti->id, $payload->jti->type, $payload->auth ?? ''];
    }

    /**
     * 验证Token
     */
    public function verifyToken()
    {
        JWT::$leeway = 60;
        JWT::decode($this->token, env('app.app_key'), ['HS256']);
    }

    /**
     * 创建Token并存入令牌桶
     */
    public function createToken(int $id, string $type, array $params = [])
    {
        $tokenInfo = $this->getToken($id, $type, $params);
        $exp = $tokenInfo['params']['exp'] - $tokenInfo['params']['iat'] + 60;

        CacheService::setTokenBucket(
            md5($tokenInfo['token']),
            ['uid' => $id, 'type' => $type, 'token' => $tokenInfo['token'], 'exp' => $exp],
            (int)$exp,
            $type
        );

        return $tokenInfo;
    }
}

使用示例

$jwtAuth = app()->make(JwtAuth::class);

// 生成Token
$tokenInfo = $jwtAuth->createToken($userId, 'api');
$token = $tokenInfo['token'];

// 解析Token
[$id, $type, $auth] = $jwtAuth->parseToken($token);

// 验证Token
try {
    $jwtAuth->verifyToken();
} catch (\Exception $e) {
    // Token无效或已过期
}

3.2 Queue - 队列工具

<?php
namespace crmeb\utils;

/**
 * 队列工具类
 * @method $this do(string $do) 设置任务执行方法
 * @method $this job(string $job) 设置任务执行类名
 * @method $this errorCount(int $errorCount) 执行失败次数
 * @method $this data(...$data) 执行数据
 * @method $this secs(int $secs) 延迟执行秒数
 */
class Queue
{
    /**
     * 放入消息队列
     */
    public function push(?array $data = null)
    {
        // 将任务放入队列
    }
}

使用示例

use crmeb\utils\Queue;
use app\jobs\order\OrderJob;

// 基本用法 - 立即执行
Queue::instance()
    ->job(OrderJob::class)
    ->data($orderId)
    ->push();

// 指定执行方法
Queue::instance()
    ->job(OrderJob::class)
    ->do('sendNotify')
    ->data($orderId, $uid)
    ->push();

// 延迟执行(5分钟后)
Queue::instance()
    ->job(OrderJob::class)
    ->secs(300)
    ->data($orderId)
    ->push();

// 设置重试次数
Queue::instance()
    ->job(OrderJob::class)
    ->errorCount(5)
    ->data($orderId)
    ->push();

3.3 ApiErrorCode - API错误码

<?php
namespace crmeb\utils;

class ApiErrorCode
{
    // 成功
    const SUCCESS = [200, 'SUCCESS'];

    // 通用错误
    const ERR_EXCEPTION = [400, '系统错误'];
    const ERR_PARAM = [400100, '参数错误'];
    const ERR_NOT_FOUND = [404, '资源不存在'];

    // 认证错误
    const ERR_LOGIN = [410000, '请登录'];
    const ERR_LOGIN_INVALID = [410001, '登录已过期,请重新登录'];
    const ERR_LOGIN_STATUS = [410002, '登录状态有误,请重新登录'];
    const ERR_SITE_UPGRADE = [410010, '站点升级中,请稍候访问'];
    const ERR_BAN_LOGIN = [410020, '您已被禁止登录,请联系管理员'];

    // 权限错误
    const ERR_AUTH = [403, '没有权限'];

    // Token错误
    const ERR_SAVE_TOKEN = [400200, 'token保存失败'];
}

3.4 Captcha - 验证码

<?php
namespace crmeb\utils;

class Captcha
{
    /**
     * 生成验证码
     */
    public function create($id = '')
    {
        // 生成图形验证码
    }

    /**
     * 验证验证码
     */
    public function check(string $code, $id = ''): bool
    {
        // 验证图形验证码
    }
}

四、接口定义 (interfaces/)

4.1 JobInterface - 队列任务接口

<?php
namespace crmeb\interfaces;

use think\queue\Job;

interface JobInterface
{
    /**
     * 执行队列任务
     * @param Job $job
     * @param mixed $data
     */
    public function fire(Job $job, $data): void;
}

4.2 MiddlewareInterface - 中间件接口

<?php
namespace crmeb\interfaces;

interface MiddlewareInterface
{
    /**
     * 处理请求
     * @param \think\Request $request
     * @param \Closure $next
     * @return mixed
     */
    public function handle($request, \Closure $next);
}

4.3 ListenerInterface - 监听器接口

<?php
namespace crmeb\interfaces;

interface ListenerInterface
{
    /**
     * 处理事件
     * @param mixed $event
     */
    public function handle($event);
}

五、异常类 (exceptions/)

5.1 使用异常类

use crmeb\exceptions\AdminException;
use crmeb\exceptions\ApiException;
use crmeb\exceptions\AuthException;
use crmeb\exceptions\PayException;

// 后台异常
throw new AdminException('操作失败', 400);

// API异常
throw new ApiException('参数错误');

// 认证异常
throw new AuthException('请先登录', 410000);

// 支付异常
throw new PayException('支付失败');

六、Trait特性 (traits/)

6.1 ErrorTrait - 错误处理

<?php
namespace crmeb\traits;

trait ErrorTrait
{
    protected $error = '';

    /**
     * 设置错误信息
     */
    protected function setError(string $error)
    {
        $this->error = $error;
        return false;
    }

    /**
     * 获取错误信息
     */
    public function getError(): string
    {
        return $this->error;
    }
}

6.2 ServicesTrait - 服务特性

<?php
namespace crmeb\traits;

trait ServicesTrait
{
    /**
     * 事务处理
     */
    public function transaction(callable $callback)
    {
        return \think\facade\Db::transaction($callback);
    }
}

七、表单构建器 (form/)

7.1 使用表单构建器

use crmeb\services\FormBuilder;

// 创建表单
$form = FormBuilder::createForm('admin/user/save', [
    FormBuilder::input('username', '用户名')->required(),
    FormBuilder::input('password', '密码')->type('password'),
    FormBuilder::select('status', '状态', 1)->options([
        ['value' => 1, 'label' => '启用'],
        ['value' => 0, 'label' => '禁用']
    ]),
    FormBuilder::frameImage('avatar', '头像', '/admin/widget.images'),
    FormBuilder::dateTime('birthday', '生日'),
    FormBuilder::radio('gender', '性别', 1)->options([
        ['value' => 1, 'label' => '男'],
        ['value' => 2, 'label' => '女']
    ])
]);

return $form->setMethod('post')->setTitle('添加用户');

八、最佳实践

8.1 扩展基础类

<?php
namespace app\services\order;

use crmeb\basic\BaseJobs;

class MyOrderJob extends BaseJobs
{
    public function doJob(int $orderId): bool
    {
        // 自定义处理逻辑
        return true;
    }
}

8.2 使用缓存服务

// 带锁的操作
CacheService::lock('order_' . $orderId, function() use ($orderId) {
    // 原子操作
    return $this->processOrder($orderId);
});

// 使用Remember模式
$data = CacheService::remember('key', function() {
    return $this->expensiveOperation();
}, 3600);

8.3 队列任务设计

// 推荐:任务参数简洁
Queue::instance()
    ->job(OrderJob::class)
    ->data($orderId)  // 只传ID
    ->push();

// 在任务中查询完整数据
public function doJob(int $orderId): bool
{
    $order = $this->orderService->get($orderId);
    // 处理逻辑
}

常见问题

Q1: 如何自定义上传驱动?

继承 BaseUpload 类并实现抽象方法。

Q2: 如何扩展缓存功能?

使用 CacheService::redisHandler() 获取Redis实例,直接调用Redis命令。

Q3: JWT Token过期时间如何调整?

修改 JwtAuth::getToken() 方法中的 $exp_time 计算逻辑。

Q4: 如何添加新的异常类型?

继承 \Exception 类,放置在 crmeb/exceptions/ 目录。

{{cateWiki.like_num}}人点赞
0人点赞
评论({{cateWiki.comment_num}}) {{commentWhere.order ? '评论从旧到新':'评论从新到旧'}} {{cateWiki.page_view_num}}人看过该文档
评论(0) {{commentWhere.order ? '评论从旧到新':'评论从新到旧'}} 8人看过该文档
评论
{{item.user ? item.user.nickname : ''}} (自评)
{{item.content}}
{{item.create_time}} 删除
{{item.like ? item.like.like_num : 0}} {{replyIndex == index ? '取消回复' : '回复'}}
评论
{{items.user ? items.user.nickname : '暂无昵称'}} (自评)
{{items.content}}
{{items.create_time}} 删除
{{items.like ? items.like.like_num : 0}} {{replyIndexJ == (index+'|'+indexJ) ? '取消回复' : '回复'}}
评论
目录
  • {{item}}