| 
...
 | 
...
 | 
@@ -2,6 +2,9 @@ | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 namespace Lib;
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 use Swoole\Database\PDOConfig;
 | 
| 
 | 
 | 
 use Swoole\Database\PDOPool;
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 /**
 | 
| 
 | 
 | 
  * db 池
 | 
| 
 | 
 | 
  * @author:dc
 | 
| 
...
 | 
...
 | 
@@ -11,384 +14,43 @@ namespace Lib; | 
| 
 | 
 | 
  */
 | 
| 
 | 
 | 
 class DbPool {
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * @var \Lib\DbPool[]
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     static $instance = [];
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     use DbQuery;
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * @var \PDO
 | 
| 
 | 
 | 
      * @var \Swoole\Database\PDOPool
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     private $client;
 | 
| 
 | 
 | 
     static $pool = null;
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * @var
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     private $cid;
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     public $lastTimer;
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * @return mixed
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/13 9:12
 | 
| 
 | 
 | 
      * 实例
 | 
| 
 | 
 | 
      * DbPool constructor.
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function getClient(){
 | 
| 
 | 
 | 
         if(!$this->client){
 | 
| 
 | 
 | 
             $this->connect();
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         try {
 | 
| 
 | 
 | 
             // 判断是否链接中
 | 
| 
 | 
 | 
             if($this->client->getAttribute(\PDO::ATTR_CONNECTION_STATUS)===false){
 | 
| 
 | 
 | 
                 $this->connect();
 | 
| 
 | 
 | 
             }
 | 
| 
 | 
 | 
         }catch (\Throwable $e){
 | 
| 
 | 
 | 
             $this->connect();
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $this->lastTimer = time();
 | 
| 
 | 
 | 
         return $this->client;
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     public function __construct($cid)
 | 
| 
 | 
 | 
     public function __construct()
 | 
| 
 | 
 | 
     {
 | 
| 
 | 
 | 
         $this->cid = $cid;
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $this->connect();
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 连接sql
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/3/23 9:14
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function connect(){
 | 
| 
 | 
 | 
         $this->lastTimer = time();
 | 
| 
 | 
 | 
         $tryNum = 0;
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         DBPOOLCONNECTFOR:
 | 
| 
 | 
 | 
         try {
 | 
| 
 | 
 | 
             $this->client = new \PDO(
 | 
| 
 | 
 | 
                 'mysql:charset=utf8mb4;dbname='.DB_DATABASE.';host='.DB_HOST.';port='.DB_PORT,
 | 
| 
 | 
 | 
                 DB_USER,
 | 
| 
 | 
 | 
                 DB_PASSWORD,
 | 
| 
 | 
 | 
                 [
 | 
| 
 | 
 | 
         if(!static::$pool){
 | 
| 
 | 
 | 
             $pdoconfig = (new PDOConfig)
 | 
| 
 | 
 | 
                 ->withHost(DB_HOST)
 | 
| 
 | 
 | 
                 ->withPort(DB_PORT)
 | 
| 
 | 
 | 
                 ->withDbName(DB_DATABASE)
 | 
| 
 | 
 | 
                 ->withCharset('utf8mb4')
 | 
| 
 | 
 | 
                 ->withUsername(DB_USER)
 | 
| 
 | 
 | 
                 ->withPassword(DB_PASSWORD)
 | 
| 
 | 
 | 
                 ->withOptions([
 | 
| 
 | 
 | 
                     \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
 | 
| 
 | 
 | 
                     \PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8mb4'",
 | 
| 
 | 
 | 
                     \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
 | 
| 
 | 
 | 
                 ]
 | 
| 
 | 
 | 
             );
 | 
| 
 | 
 | 
         }catch (\Throwable $e){
 | 
| 
 | 
 | 
             // 重新链接3次
 | 
| 
 | 
 | 
             if($tryNum < 3){
 | 
| 
 | 
 | 
                 $tryNum++;
 | 
| 
 | 
 | 
                 goto DBPOOLCONNECTFOR;
 | 
| 
 | 
 | 
             }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
             logs($e->getMessage().$e->getTraceAsString());
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 查询
 | 
| 
 | 
 | 
      * @param string|array $sql
 | 
| 
 | 
 | 
      * @return false|\PDOStatement
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/17 10:01
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     private function query(string|array $sql){
 | 
| 
 | 
 | 
         if(is_array($sql)){
 | 
| 
 | 
 | 
             list($sql,$params) = $sql;
 | 
| 
 | 
 | 
         }else{
 | 
| 
 | 
 | 
             $params = null;
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         if(APP_DEBUG) {
 | 
| 
 | 
 | 
             $timer = microtime(true);
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         try {
 | 
| 
 | 
 | 
             $query = $this->getClient()->prepare($sql);
 | 
| 
 | 
 | 
             $ret = $query->execute($params);
 | 
| 
 | 
 | 
         }catch (\Throwable $e){
 | 
| 
 | 
 | 
             logs([
 | 
| 
 | 
 | 
                 $sql,$params,
 | 
| 
 | 
 | 
                 $e->getMessage(),
 | 
| 
 | 
 | 
                 $e->getTraceAsString()
 | 
| 
 | 
 | 
             ],
 | 
| 
 | 
 | 
                 LOG_PATH.'/'.date('Y-m-d').'-sql.error.log'
 | 
| 
 | 
 | 
             );
 | 
| 
 | 
 | 
             $ret = false;
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         if(APP_DEBUG){
 | 
| 
 | 
 | 
             $timer2  = microtime(true);
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
             // todo:: 记录日志,生产请注释
 | 
| 
 | 
 | 
             $sql = '['.substr(($timer2-$timer)*1000,0,6).'ms] '.$sql;
 | 
| 
 | 
 | 
             logs(
 | 
| 
 | 
 | 
                 $params ? [$sql,$params] : $sql,
 | 
| 
 | 
 | 
                 LOG_PATH.'/'.date('Y-m-d').'.sql.log'
 | 
| 
 | 
 | 
             );
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         if($ret){
 | 
| 
 | 
 | 
             return $query;
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         return false;
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 更新数据
 | 
| 
 | 
 | 
      * @param string $table
 | 
| 
 | 
 | 
      * @param array $data
 | 
| 
 | 
 | 
      * @param string $where
 | 
| 
 | 
 | 
      * @param bool $timeauto
 | 
| 
 | 
 | 
      * @return int
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/17 14:03
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function update(string $table, array $data, string $where, $timeauto = true):int {
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         if($timeauto){
 | 
| 
 | 
 | 
             $data['updated_at'] = empty($data['updated_at']) ? date('Y-m-d H:i:s') : $data['updated_at'];
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $sql = "update `{$table}` set ".dbUpdate($data). " where ".$where;
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $data = $this->getData($data);
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $query = $this->query([$sql,$data]);
 | 
| 
 | 
 | 
         if($query){
 | 
| 
 | 
 | 
             return $query->rowCount();
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
         return 0;
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 在更新/插入时处理数据
 | 
| 
 | 
 | 
      * @param $data
 | 
| 
 | 
 | 
      * @return mixed
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/18 14:50
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     private function getData($data){
 | 
| 
 | 
 | 
         // 如果存储的值是数组,就json一次
 | 
| 
 | 
 | 
         foreach ($data as $k=>$datum){
 | 
| 
 | 
 | 
             if(is_array($datum)){
 | 
| 
 | 
 | 
                 $data[$k] = json_encode($datum,JSON_UNESCAPED_UNICODE);
 | 
| 
 | 
 | 
             }elseif ($datum === null){
 | 
| 
 | 
 | 
                 $data[$k] = '';
 | 
| 
 | 
 | 
             }
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
         return $data;
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 插入数据
 | 
| 
 | 
 | 
      * @param string $table
 | 
| 
 | 
 | 
      * @param array $data
 | 
| 
 | 
 | 
      * @param bool $timeauto
 | 
| 
 | 
 | 
      * @return int
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/17 14:04
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function insert(string $table, array $data, $timeauto = true):int {
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         if($timeauto){
 | 
| 
 | 
 | 
             $data['created_at'] = empty($data['created_at']) ? date('Y-m-d H:i:s') : $data['created_at'];
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $sql = "insert into `{$table}` set ".dbUpdate($data);
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $data = $this->getData($data);
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $query = $this->query([$sql,$data]);
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         if($query){
 | 
| 
 | 
 | 
             return $this->getClient()->lastInsertId();
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
                 ]);
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         return 0;
 | 
| 
 | 
 | 
             static::$pool = new PDOPool($pdoconfig,1024);
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 删除语句 软删
 | 
| 
 | 
 | 
      * @param string $table
 | 
| 
 | 
 | 
      * @param array $where
 | 
| 
 | 
 | 
      * @param null $upFiled
 | 
| 
 | 
 | 
      * @return int
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/4/11 14:47
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function delete(string $table, array $where,$upFiled=null){
 | 
| 
 | 
 | 
         // 获取链接
 | 
| 
 | 
 | 
         $this->client = static::$pool->get();
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         if($upFiled){
 | 
| 
 | 
 | 
             return $this->update($table,[$upFiled === true ? 'deleted_at' : $upFiled =>time()],$where);
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $sql = "delete from `{$table}` where ".dbUpdate($where);
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $query = $this->query([$sql,$where]);
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         if($query){
 | 
| 
 | 
 | 
             return $query->rowCount();
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         return 0;
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 统计数量
 | 
| 
 | 
 | 
      * @param string $sql
 | 
| 
 | 
 | 
      * @return int
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/14 16:19
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function count(string|array $sql):int{
 | 
| 
 | 
 | 
         $query = $this->query($sql);
 | 
| 
 | 
 | 
         if($query){
 | 
| 
 | 
 | 
             return $query->fetch(\PDO::FETCH_COLUMN);
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
         return 0;
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 某个值
 | 
| 
 | 
 | 
      * @param string|array $sql
 | 
| 
 | 
 | 
      * @return mixed|null
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/17 11:03
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function value(string|array $sql){
 | 
| 
 | 
 | 
         $query = $this->query($sql);
 | 
| 
 | 
 | 
         if($query){
 | 
| 
 | 
 | 
             return $query->fetch(\PDO::FETCH_COLUMN);
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
         return null;
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 查询一条数据
 | 
| 
 | 
 | 
      * @param string|array $sql
 | 
| 
 | 
 | 
      * @return mixed|null
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/13 14:54
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function first(string|array $sql){
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $query = $this->query($sql);
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         if($query){
 | 
| 
 | 
 | 
             return $query->fetch();
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         return null;
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 查询列表
 | 
| 
 | 
 | 
      * @param string|array $sql
 | 
| 
 | 
 | 
      * @return mixed|null
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/13 14:54
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function all(string|array $sql){
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         $query = $this->query($sql);
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         if($query){
 | 
| 
 | 
 | 
             return $query->fetchAll();
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         return null;
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 事务开启
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/17 11:35
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function transaction(){
 | 
| 
 | 
 | 
         $this->getClient()->beginTransaction();
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 事务回滚
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/17 11:35
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function rollBack(){
 | 
| 
 | 
 | 
         $this->getClient()->rollBack();
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 事务提交
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/17 11:35
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function commit(){
 | 
| 
 | 
 | 
         $this->getClient()->commit();
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 验证是否正常连接
 | 
| 
 | 
 | 
      * @return bool
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2024/4/10 10:09
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function ping(){
 | 
| 
 | 
 | 
         try {
 | 
| 
 | 
 | 
             $query = $this->getClient()->query("select 200;");
 | 
| 
 | 
 | 
             if($query->fetchColumn() == 200){
 | 
| 
 | 
 | 
                 return true;
 | 
| 
 | 
 | 
             }
 | 
| 
 | 
 | 
         }catch (\Throwable $e){
 | 
| 
 | 
 | 
             return false;
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         return false;
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * @param $cid
 | 
| 
 | 
 | 
      * @return DbPool
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2023/2/13 9:39
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public static function instance($cid=0){
 | 
| 
 | 
 | 
         if(empty(static::$instance[$cid])){
 | 
| 
 | 
 | 
             static::$instance[$cid] = new DbPool($cid);
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         if(!static::$instance[$cid]->ping()){
 | 
| 
 | 
 | 
             static::$instance[$cid]->close();
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
             static::$instance[$cid] = new DbPool($cid);
 | 
| 
 | 
 | 
         }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
         return static::$instance[$cid];
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
...
 | 
...
 | 
@@ -398,10 +60,14 @@ class DbPool { | 
| 
 | 
 | 
         $this->close();
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
     /**
 | 
| 
 | 
 | 
      * 关闭链接
 | 
| 
 | 
 | 
      * @author:dc
 | 
| 
 | 
 | 
      * @time 2024/5/30 10:30
 | 
| 
 | 
 | 
      */
 | 
| 
 | 
 | 
     public function close(){
 | 
| 
 | 
 | 
         self::$pool->put($this->client);
 | 
| 
 | 
 | 
         $this->client = null;
 | 
| 
 | 
 | 
         unset(static::$instance[$this->cid]);
 | 
| 
 | 
 | 
     }
 | 
| 
 | 
 | 
 
 | 
| 
 | 
 | 
 
 | 
...
 | 
...
 | 
 |