首頁  >  問答  >  主體

如何在執行覆蓋時驗證欄位和條件

我們有一個中央伺服器和幾個地區伺服器,例如d1、d2、d3、d4、d5。

有一些要進行複製的表。為了簡單起見,讓我們假設我們有一個名為tblFoo的表,它存在於d1、d2、d3、d4、d5和c上,並且在所有這些伺服器上都具有相同的結構。規則很簡單:

目標是確保如果對d伺服器上的tblFoo進行更改(插入、更新、刪除),則也應立即在c伺服器上進行更改。這對於插入操作非常好用(因為id,即pkFooID,根據定義具有auto_increment屬性)。這對於更新和刪除操作也有效,但我們對這些操作有一些擔憂。以下是(簡化版本的)程式碼:

namespace App\ORM;

use Cake\ORM\Query as ORMQuery;
// 其他一些use语句

class Query extends ORMQuery
{
    // 大量的代码...

    /**
     * 重写同名方法以处理与c的同步
     */
    public function execute()
    {
        // 一些表需要复制。如果是这样的表,则我们需要执行一些额外的步骤。否则,我们只需调用父方法
        if (($this->_repository->getIgnoreType() || (!in_array($this->type(), ['select']))) && $this->isReplicate() && ($this->getConnection()->configName() !== 'c')) {
            // 获取表
            $table = $this->_repository->getTable();
            // 复制查询
            $replica = clone $this;
            // 将复制品的连接设置为c,因为我们需要在中央应用地区的更改
            $replica->setParentConnectionType('d')->setConnection(ConnectionManager::get('c'));
            $replica->setIgnoreType($this->_repository->getIgnoreType());
            // 首先执行复制品,因为我们需要引用c的ID而不是反过来
            $replica->execute();
            // 如果是插入操作,我们还需要处理ID
            if (!empty($this->clause('insert'))) {
                // 加载主键的名称,以便稍后使用它来查找最大值
                $primaryKey = $this->_repository->getPrimaryKey();
                // 获取最高的ID值,这将始终是一个正数,因为我们已经在复制品上执行了查询
                $firstID = $replica->getConnection()
                                   ->execute("SELECT LAST_INSERT_ID() AS {$primaryKey}")
                                   ->fetchAll('assoc')[0][$primaryKey];

                // 获取列
                $columns = $this->clause('values')->getColumns();
                // 为了添加主键
                $columns[] = $primaryKey;
                // 然后用这个调整后的数组覆盖插入子句
                $this->insert($columns);
                // 获取值
                $values = $this->clause('values')->getValues();
                // 以及它们的数量
                $count = count($values);
                // 可能已经将多个行插入到复制品中作为此查询的一部分,我们需要复制所有它们的ID,而不是假设有一个单独的插入记录
                for ($index = 0; $index < $count; $index++) {
                    // 将适当的ID值添加到所有要插入的记录中
                    $values[$index][$primaryKey] = $firstID + $index;
                }
                // 用这个调整后的数组覆盖值子句,其中包含PK值
                $this->clause('values')->values($values);
            }
        }
        if ($this->isQueryDelete) {
            $this->setIgnoreType(false);
        }
        // 无论如何都执行查询,无论它是复制表还是非复制表
        // 如果是复制表,则我们已经在if块中对查询进行了调整
        return parent::execute();
    }

}

擔心的問題是:如果我們在d1上執行update或delete語句,其條件可以在其他地區伺服器(d2、d3、d4、d5)上滿足,那麼在d1上正確執行update和delete語句,但一旦相同的語句在d1上執行,我們可能會意外地從c伺服器更新/刪除其他地區的記錄。

為解決此問題,建議的解決方案是驗證語句,並在以下條件之一不滿足時拋出例外:

沒有複製行為的表將正常執行execute,上述限制僅適用於具有複製行為的表,例如我們範例中的tblFoo。

問題

如何在我的execute重寫中驗證update/delete查詢,以便只能搜尋主鍵或外鍵,並且只能使用=或IN運算子?

P粉448346289P粉448346289283 天前386

全部回覆(1)我來回復

  • P粉333186285

    P粉3331862852024-01-11 13:56:49

    這是我解決問題的方法。

    具有複製行為的模型執行以下驗證:

    <?php
    
    namespace App\ORM;
    
    use Cake\ORM\Table as ORMTable;
    
    class Table extends ORMTable
    {
        protected static $replicateTables = [
            'inteacherkeyjoin',
        ];
    
        public function isValidReplicateCondition(array $conditions)
        {
            return count(array_filter($conditions, function ($v, $k) {
                return (bool) preg_match('/^[\s]*[pf]k(' . implode('|', self::$replicateTables) . ')id[\s]*((in|=).*)?$/i', strtolower(($k === intval($k)) ? $v : $k));
            }, ARRAY_FILTER_USE_BOTH)) > 0;
        }
    
        public function validateUpdateDeleteCondition($action, $conditions)
        {
            if ($this->behaviors()->has('Replicate')) {
                if (!is_array($conditions)) {
                    throw new \Exception("在调用{$action}时,需要传递一个数组");
                } elseif (!$this->isValidReplicateCondition($conditions)) {
                    throw new \Exception("传递给{$action}操作的条件不安全,您需要指定主键或带有=或IN运算符的外键");
                }
            }
        }
    
        public function query()
        {
            return new Query($this->getConnection(), $this);
        }
    }

    對於Query類,我們有一個isReplicate方法,觸發我們需要的驗證,where方法已被重寫,以確保條件被正確驗證:

    /**
         * 当且仅当:
         * - _repository已正确初始化
         * - _repository具有复制行为
         * - 当前连接不是c
         */
        protected function isReplicate()
        {
            if (($this->type() !== 'select') && ($this->getConnection()->configName() === 'c') && ($this->getParentConnectionType() !== 'd')) {
                throw new \Exception('副本表必须始终从区域连接更改');
            }
            if (in_array($this->type(), ['update', 'delete'])) {
                $this->_repository->validateUpdateDeleteCondition($this->type(), $this->conditions);
            }
    
            return ($this->_repository && $this->_repository->behaviors()->has('Replicate'));
        }
    
        public function where($conditions = null, $types = [], $overwrite = false)
        {
            $preparedConditions = is_array($conditions) ? $conditions : [$conditions];
            $this->conditions = array_merge($this->conditions, $preparedConditions);
    
            return parent::where($conditions, $types, $overwrite);
        }

    回覆
    0
  • 取消回覆