## 任务占用与边界 - 任务名称:全项目上游业务镜像目录与通用同步框架 - 实际开发者:WuYongJie - 目标结果:登记当前代码实际使用的上游原表,提供低压、可断点的首次全量、增量重叠和全量校准框架。 - 实际修改:13 个文件;新增通用数据集目录、动态镜像建表、同步状态、同步命令、生产低压轮询、测试和团队清单。 - 将写入的公共契约:`business_mirror_table_states`、`business-mirror:sync-tables`、`BusinessMirrorDatasetRegistry`、`BusinessMirrorSchemaManager`。 - 仅复用、明确不修改:`UpstreamReadOnlyQueryFactory`、`upstream_schema_columns`、既有 `product_price_mirror_rows`。 - 明确不包含:本 PR 不切换其余业务页面 Repository,不执行生产迁移或首次全量,不提交真实业务数据或凭据。 ## 合并前自审 - [x] 已重新拉取最新 `origin/main`,分支仅领先 1 个提交 - [x] `git merge-tree --write-tree origin/main HEAD` 无冲突 - [x] 完整 diff 共 13 个文件,未发现越界文件或敏感内容 - [x] 生产共享数据字典核对 38 个数据集,缺失对象/字段为 0 - [x] 临时 MariaDB 空库迁移实测成功,验证库已删除 - [x] 上游异常空结果不会清空已有镜像 - [x] 定时任务只轮转已完成首次全量的数据集,不会自动扫描未初始化大表 ## 测试 - `BusinessMirrorSynchronizerTest` 等相关测试:14 tests / 65 assertions 全通过 - 新同步器专项:5 tests / 25 assertions 全通过 - 新增 PHP 文件语法检查全部通过 - 两份生产激活脚本 `bash -n` 通过 - `git diff --check` 通过 ## 风险与回滚 - 包含新增迁移,但发布脚本遇到迁移仍会要求独立高风险授权;合并本身不会执行生产迁移。 - 首次全量必须逐数据集显式执行并做数量/翻页/筛选验收,特别是约 280 万行的大表不得随发布自动全扫。 - 回滚代码可回退本合并提交;生产迁移执行前必须备份 `caiwu_system_config` 与 `caiwu_business_mirror`。 Reviewed-on: http://git.1080p.hk/WuYongJie/CaiWuXiTong/pulls/53
79 lines
3.1 KiB
PHP
79 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace Modules\EcommerceFinance\Services;
|
|
|
|
use InvalidArgumentException;
|
|
use Modules\EcommerceFinance\Contracts\BusinessMirrorDataset;
|
|
|
|
class BusinessMirrorDatasetRegistry
|
|
{
|
|
/** @return array<string,BusinessMirrorDataset> */
|
|
public function all(): array
|
|
{
|
|
$datasets = [];
|
|
|
|
foreach ((array) config('upstream_mirrors.datasets', []) as $key => $definition) {
|
|
$datasets[(string) $key] = $this->make((string) $key, (array) $definition);
|
|
}
|
|
|
|
return $datasets;
|
|
}
|
|
|
|
public function get(string $key): BusinessMirrorDataset
|
|
{
|
|
$definitions = (array) config('upstream_mirrors.datasets', []);
|
|
$definition = (array) ($definitions[$key] ?? []);
|
|
|
|
if ($definition === []) {
|
|
throw new InvalidArgumentException('未知的业务镜像数据集:' . $key);
|
|
}
|
|
|
|
return $this->make($key, $definition);
|
|
}
|
|
|
|
private function make(string $key, array $definition): BusinessMirrorDataset
|
|
{
|
|
$source = trim((string) ($definition['source_key'] ?? ''));
|
|
$schema = trim((string) ($definition['schema'] ?? ''));
|
|
$object = trim((string) ($definition['object'] ?? ''));
|
|
$target = trim((string) ($definition['target'] ?? ''));
|
|
$keys = array_values(array_filter(array_map('strval', (array) ($definition['keys'] ?? []))));
|
|
$watermark = trim((string) ($definition['watermark'] ?? ''));
|
|
|
|
foreach ([$source, $schema, $object, $target, ...$keys] as $identifier) {
|
|
if (preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $identifier) !== 1) {
|
|
throw new InvalidArgumentException('业务镜像数据集包含不安全的标识符:' . $key);
|
|
}
|
|
}
|
|
|
|
if ($keys === [] || strlen($target) > 64) {
|
|
throw new InvalidArgumentException('业务镜像数据集缺少稳定键或目标表名过长:' . $key);
|
|
}
|
|
|
|
if ($watermark !== '' && preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $watermark) !== 1) {
|
|
throw new InvalidArgumentException('业务镜像数据集的增量字段不安全:' . $key);
|
|
}
|
|
|
|
return new readonly class($key, $source, $schema, $object, $target, $keys, $watermark ?: null) implements BusinessMirrorDataset {
|
|
public function __construct(
|
|
private string $key,
|
|
private string $source,
|
|
private string $schema,
|
|
private string $object,
|
|
private string $target,
|
|
private array $keys,
|
|
private ?string $watermark,
|
|
) {
|
|
}
|
|
|
|
public function key(): string { return $this->key; }
|
|
public function sourceKey(): string { return $this->source; }
|
|
public function sourceSchema(): string { return $this->schema; }
|
|
public function sourceObject(): string { return $this->object; }
|
|
public function targetTable(): string { return $this->target; }
|
|
public function keyColumns(): array { return $this->keys; }
|
|
public function watermarkColumn(): ?string { return $this->watermark; }
|
|
};
|
|
}
|
|
}
|