Maison > Questions et réponses > le corps du texte
Nous avons un serveur central et plusieurs serveurs régionaux tels que d1, d2, d3, d4, d5.
Il y a quelques tableaux à copier. Pour simplifier, supposons que nous ayons une table appelée tblFoo qui existe sur d1, d2, d3, d4, d5 et c et qui a la même structure sur tous ces serveurs. Les règles sont simples :
L'objectif est de garantir que si une modification (insertion, mise à jour, suppression) est apportée à tblFoo sur le serveur d, elle doit également être immédiatement modifiée sur le serveur c. Cela fonctionne très bien pour les opérations d'insertion (car l'identifiant, qui est pkFooID, possède par définition l'attribut auto_increment). Cela fonctionne également pour les opérations de mise à jour et de suppression, mais nous avons quelques problèmes avec ces opérations. Voici le code (version simplifiée) :
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(); } }
Le souci est : Si nous exécutons des instructions update ou delete sur d1, dont les conditions peuvent être satisfaites sur d'autres serveurs régionaux (d2, d3, d4, d5), alors les instructions update et delete sont exécutées correctement sur d1, mais une fois la même instruction est exécutée sur d1, nous pouvons accidentellement mettre à jour/supprimer des enregistrements dans d'autres régions du serveur c.
Pour résoudre ce problème, la solution recommandée est de valider l'instruction et de lever une exception si l'une des conditions suivantes n'est pas remplie :
Les tables sans comportement de réplication s'exécuteront normalement. Les restrictions ci-dessus s'appliquent uniquement aux tables avec un comportement de réplication, telles que tblFoo dans notre exemple.
Comment puis-je valider les requêtes de mise à jour/suppression dans ma réécriture d'exécution afin que seules les clés primaires ou étrangères puissent être recherchées et que seuls les opérateurs = ou IN puissent être utilisés ?
P粉3331862852024-01-11 13:56:49
C'est ainsi que j'ai résolu mon problème.
Les modèles avec comportement de copie effectuent les validations suivantes :
<?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); } }
Pour Query
类,我们有一个isReplicate
方法,触发我们需要的验证,where
, les méthodes ont été remplacées pour garantir que les conditions sont correctement validées :
/** * 当且仅当: * - _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); }