Heim  >  Fragen und Antworten  >  Hauptteil

So validieren Sie Felder und Bedingungen beim Durchführen von Überschreibungen

Wir haben einen zentralen Server und mehrere regionale Server wie d1, d2, d3, d4, d5.

Es müssen einige Tabellen kopiert werden. Nehmen wir der Einfachheit halber an, dass wir eine Tabelle namens tblFoo haben, die auf d1, d2, d3, d4, d5 und c existiert und auf allen diesen Servern die gleiche Struktur hat. Die Regeln sind einfach:

Das Ziel besteht darin, sicherzustellen, dass eine Änderung (Einfügen, Aktualisieren, Löschen) an tblFoo auf dem D-Server sofort auch auf dem C-Server geändert werden sollte. Dies funktioniert hervorragend für Einfügevorgänge (da die ID, die pkFooID ist, per Definition über das Attribut auto_increment verfügt). Dies funktioniert auch für Aktualisierungs- und Löschvorgänge, wir haben jedoch einige Bedenken hinsichtlich dieser Vorgänge. Hier ist der Code (vereinfachte Version):

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();
    }

}

Die Sorge ist : Wenn wir auf d1 Aktualisierungs- oder Löschanweisungen ausführen, deren Bedingungen auf anderen regionalen Servern (d2, d3, d4, d5) erfüllt werden können, werden die Aktualisierungs- und Löschanweisungen auf d1 korrekt ausgeführt, jedoch nur einmal Wenn dieselbe Anweisung auf d1 ausgeführt wird, können wir versehentlich Datensätze in anderen Regionen vom C-Server aktualisieren/löschen.

Um dieses Problem zu lösen, besteht die empfohlene Lösung darin, die Anweisung zu validieren und eine Ausnahme auszulösen, wenn eine der folgenden Bedingungen nicht erfüllt ist:

Tabellen ohne Replikationsverhalten werden normal ausgeführt. Die oben genannten Einschränkungen gelten nur für Tabellen mit Replikationsverhalten, wie z. B. tblFoo in unserem Beispiel.

Frage

Wie kann ich Aktualisierungs-/Löschabfragen in meinem Execute-Rewrite validieren, sodass nur Primär- oder Fremdschlüssel durchsucht werden können und nur =- oder IN-Operatoren verwendet werden können?

P粉448346289P粉448346289283 Tage vor384

Antworte allen(1)Ich werde antworten

  • 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);
        }

    Antwort
    0
  • StornierenAntwort