81 lines
3.7 KiB
PHP
81 lines
3.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
$root = dirname(__DIR__, 2);
|
|
$paths = [
|
|
'controller' => __DIR__ . '/../app/adminapi/controller/qywx/CustomerController.php',
|
|
'logic' => __DIR__ . '/../app/adminapi/logic/qywx/CustomerLogic.php',
|
|
'validate' => __DIR__ . '/../app/adminapi/validate/qywx/CustomerValidate.php',
|
|
'api' => $root . '/admin/src/api/qywx.ts',
|
|
'page' => $root . '/admin/src/views/fans/qywx.vue',
|
|
'migration' => __DIR__ . '/../sql/1.9.20260902/add_qywx_customer_delete_menu.sql',
|
|
];
|
|
|
|
$sources = [];
|
|
foreach ($paths as $name => $path) {
|
|
$source = file_get_contents($path);
|
|
if (!is_string($source)) {
|
|
throw new RuntimeException("无法读取 {$name}: {$path}");
|
|
}
|
|
$sources[$name] = $source;
|
|
}
|
|
|
|
function qywxDeleteExpect(bool $condition, string $message): void
|
|
{
|
|
if (!$condition) {
|
|
throw new RuntimeException($message);
|
|
}
|
|
}
|
|
|
|
$controller = $sources['controller'];
|
|
$permissionCheck = strpos($controller, 'if (!$this->canDeleteCustomer())');
|
|
$deleteCall = strpos($controller, 'CustomerLogic::deleteCustomer(');
|
|
qywxDeleteExpect(str_contains($controller, "private const DELETE_PERMISSION = 'qywx.customer/delete';"), '控制器缺少独立删除权限');
|
|
qywxDeleteExpect($permissionCheck !== false && $deleteCall !== false && $permissionCheck < $deleteCall, '控制器必须在删除前显式鉴权');
|
|
qywxDeleteExpect(str_contains($controller, "(int) (\$this->adminInfo['root'] ?? 0) === 1")
|
|
&& str_contains($controller, 'AuthLogic::getAuthByAdminId($this->adminId)')
|
|
&& str_contains($controller, 'in_array(self::DELETE_PERMISSION,'), '控制器删除权限必须仅放行 root 或显式授权账号');
|
|
|
|
qywxDeleteExpect(str_contains($sources['validate'], "'id' => 'require|integer|gt:0'")
|
|
&& str_contains($sources['validate'], 'public function sceneDelete()'), '删除请求缺少正整数 ID 校验');
|
|
|
|
$logic = $sources['logic'];
|
|
$logicStart = strpos($logic, 'public static function deleteCustomer(int $id): bool');
|
|
$logicEnd = strpos($logic, 'public static function softDeleteExternalContactRow(', $logicStart === false ? 0 : $logicStart);
|
|
qywxDeleteExpect($logicStart !== false && $logicEnd !== false, '无法定位客户删除逻辑');
|
|
$method = substr($logic, $logicStart, $logicEnd - $logicStart);
|
|
foreach ([
|
|
"->where('id', \$id)",
|
|
"->whereNull('delete_time')",
|
|
"'delete_time' => \$now",
|
|
"->where('external_userid', \$externalUserId)",
|
|
'if ($activeRows === 0)',
|
|
"Db::name('qywx_external_contact_tag')",
|
|
'MediaChannelService::forgetCurrentTagCatalogCache()',
|
|
] as $needle) {
|
|
qywxDeleteExpect(str_contains($method, $needle), "客户删除逻辑缺少契约:{$needle}");
|
|
}
|
|
qywxDeleteExpect(!str_contains($method, 'WechatWorkService'), '后台删除不得调用企微接口删除外部客户关系');
|
|
|
|
qywxDeleteExpect(str_contains($sources['api'], '/qywx.customer/delete')
|
|
&& str_contains($sources['api'], 'qywxCustomerDelete'), '前端缺少客户删除 API');
|
|
foreach ([
|
|
"v-perms=\"['qywx.customer/delete']\"",
|
|
'handleDelete(row)',
|
|
'仅删除系统内的同步记录',
|
|
'qywxCustomerDelete({ id })',
|
|
'Promise.all([getLists(), loadStats(), loadTagStats()])',
|
|
] as $needle) {
|
|
qywxDeleteExpect(str_contains($sources['page'], $needle), "客户列表删除交互缺少:{$needle}");
|
|
}
|
|
|
|
$migration = $sources['migration'];
|
|
qywxDeleteExpect(str_contains($migration, "`component` = 'fans/qywx'")
|
|
&& str_contains($migration, "'qywx.customer/delete'")
|
|
&& str_contains($migration, "'A'")
|
|
&& str_contains($migration, 'NOT EXISTS'), '删除权限迁移必须按页面定位并可重复执行');
|
|
qywxDeleteExpect(!str_contains(strtolower($migration), 'system_role_menu'), '删除权限不得自动授予已有角色');
|
|
|
|
echo "QYWX_CUSTOMER_DELETE_PERMISSION_CONTRACT_OK\n";
|