first
This commit is contained in:
31
server-api/tools/db_counts.php
Normal file
31
server-api/tools/db_counts.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
foreach (['message_templates', 'message_rules', 'message_logs', 'user_messages', 'tickets', 'ticket_messages'] as $table) {
|
||||
$count = $pdo->query("SELECT COUNT(*) AS c FROM {$table}")->fetchColumn();
|
||||
echo $table, ':', $count, PHP_EOL;
|
||||
}
|
||||
56
server-api/tools/db_import.php
Normal file
56
server-api/tools/db_import.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$schemaFile = dirname(__DIR__) . '/database/schema.sql';
|
||||
if (!is_file($schemaFile)) {
|
||||
fwrite(STDERR, "Schema file not found.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$sql = file_get_contents($schemaFile);
|
||||
if ($sql === false) {
|
||||
fwrite(STDERR, "Unable to read schema file.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
$statements = preg_split('/;\s*[\r\n]+/', $sql);
|
||||
$count = 0;
|
||||
|
||||
foreach ($statements as $statement) {
|
||||
$statement = trim($statement);
|
||||
if ($statement === '') {
|
||||
continue;
|
||||
}
|
||||
$pdo->exec($statement);
|
||||
$count++;
|
||||
}
|
||||
|
||||
$totalTables = (int)$pdo->query('SELECT COUNT(*) AS c FROM information_schema.tables WHERE table_schema = DATABASE()')->fetchColumn();
|
||||
|
||||
echo "IMPORT_OK\n";
|
||||
echo "STATEMENTS={$count}\n";
|
||||
echo "TABLES={$totalTables}\n";
|
||||
37
server-api/tools/db_inspect.php
Normal file
37
server-api/tools/db_inspect.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_NUM,
|
||||
]
|
||||
);
|
||||
|
||||
$tables = $pdo->query('SHOW TABLES')->fetchAll();
|
||||
|
||||
if (!$tables) {
|
||||
echo "NO_TABLES\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
foreach ($tables as $table) {
|
||||
echo $table[0], PHP_EOL;
|
||||
}
|
||||
331
server-api/tools/db_seed.php
Normal file
331
server-api/tools/db_seed.php
Normal file
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
$tables = [
|
||||
'shipping_warehouses',
|
||||
'order_shipping_targets',
|
||||
'material_tag_scan_logs', 'material_batch_download_logs', 'material_tag_codes', 'material_batches',
|
||||
'enterprise_webhook_deliveries', 'enterprise_order_events', 'enterprise_customer_order_refs', 'enterprise_api_nonces', 'enterprise_customer_apps', 'enterprise_customers',
|
||||
'user_api_tokens', 'sms_code_logs',
|
||||
'admin_api_tokens', 'admin_role_permissions', 'admin_permissions', 'admin_role_relations', 'admin_roles', 'operation_logs', 'system_configs', 'admin_users',
|
||||
'ticket_messages', 'tickets',
|
||||
'user_messages', 'message_logs', 'message_rules', 'message_templates',
|
||||
'upload_template_items', 'upload_templates',
|
||||
'report_verifies', 'report_contents', 'reports',
|
||||
'appraisal_task_key_points', 'appraisal_task_results', 'appraisal_task_reviews', 'appraisal_task_logs', 'appraisal_tasks',
|
||||
'order_supplement_task_items', 'order_supplement_tasks', 'order_timelines', 'order_extras', 'order_products', 'orders',
|
||||
'catalog_brand_categories', 'catalog_brands', 'catalog_categories',
|
||||
'user_addresses', 'user_auths', 'users',
|
||||
];
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$pdo->exec("TRUNCATE TABLE {$table}");
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$userPasswordHash = password_hash('User@123456', PASSWORD_BCRYPT);
|
||||
$adminPasswordHash = password_hash('Admin@123456', PASSWORD_BCRYPT);
|
||||
|
||||
$pdo->exec("
|
||||
INSERT INTO users (id, nickname, avatar, mobile, password, status, created_at, updated_at) VALUES
|
||||
(1, '安心验体验用户', '', '13800000000', '{$userPasswordHash}', 'enabled', '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO user_addresses (id, user_id, consignee, mobile, province, city, district, detail_address, is_default, created_at, updated_at) VALUES
|
||||
(1, 1, '安心验体验用户', '13800000000', '广东省', '深圳市', '南山区', '科技园测试路 88 号', 1, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO shipping_warehouses (id, warehouse_name, warehouse_code, warehouse_type, service_provider, receiver_name, receiver_mobile, province, city, district, detail_address, service_time, notice, supported_category_ids_json, service_area_provinces_json, service_area_cities_json, status, is_default, sort_order, remark, created_at, updated_at) VALUES
|
||||
(1, '安心验鉴定中心', 'AXY-WH-DEFAULT', 'detection_center', 'anxinyan', '安心验鉴定中心', '400-800-1314', '广东省', '深圳市', '南山区', '科技园鉴定路 88 号 安心验收件中心', '周一至周日 09:30-18:30', '寄送前请确认订单信息完整,包裹内附上订单号可提升签收后的处理效率。', NULL, NULL, NULL, 'enabled', 1, 1, '默认仓库', '{$now}', '{$now}'),
|
||||
(2, '中检合作鉴定中心', 'ZJ-WH-DEFAULT', 'detection_center', 'zhongjian', '中检合作鉴定中心', '400-800-1314', '广东省', '深圳市', '南山区', '科技园鉴定路 88 号 安心验中检收件中心', '周一至周日 09:30-18:30', '中检鉴定订单请优先附上鉴定单号,寄出后尽快填写运单号。', NULL, NULL, NULL, 'enabled', 1, 1, '默认仓库', '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO catalog_categories (id, name, code, sort_order, is_enabled, need_shipping, supported_service_types, created_at, updated_at) VALUES
|
||||
(1, '奢侈品箱包', 'luxury_bag', 1, 1, 1, JSON_ARRAY('anxinyan', 'zhongjian'), '{$now}', '{$now}'),
|
||||
(2, '潮流鞋类', 'sneaker', 2, 1, 1, JSON_ARRAY('anxinyan', 'zhongjian'), '{$now}', '{$now}'),
|
||||
(3, '腕表', 'watch', 3, 1, 1, JSON_ARRAY('anxinyan', 'zhongjian'), '{$now}', '{$now}'),
|
||||
(4, '首饰配饰', 'jewelry', 4, 1, 1, JSON_ARRAY('anxinyan', 'zhongjian'), '{$now}', '{$now}'),
|
||||
(5, '3C 数码', 'digital', 5, 1, 1, JSON_ARRAY('anxinyan'), '{$now}', '{$now}'),
|
||||
(6, '高端美妆', 'beauty', 6, 1, 1, JSON_ARRAY('anxinyan'), '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO catalog_brands (id, name, en_name, code, sort_order, is_enabled, supported_service_types, created_at, updated_at) VALUES
|
||||
(1, 'Louis Vuitton', 'Louis Vuitton', 'lv', 1, 1, JSON_ARRAY('anxinyan', 'zhongjian'), '{$now}', '{$now}'),
|
||||
(2, 'Nike', 'Nike', 'nike', 2, 1, JSON_ARRAY('anxinyan', 'zhongjian'), '{$now}', '{$now}'),
|
||||
(3, 'Rolex', 'Rolex', 'rolex', 3, 1, JSON_ARRAY('anxinyan', 'zhongjian'), '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO catalog_brand_categories (id, brand_id, category_id, created_at) VALUES
|
||||
(1, 1, 1, '{$now}'),
|
||||
(2, 2, 2, '{$now}'),
|
||||
(3, 3, 3, '{$now}');
|
||||
|
||||
INSERT INTO upload_templates (id, name, code, scope_type, scope_id, service_provider, is_default, is_enabled, created_at, updated_at) VALUES
|
||||
(1, '箱包上传模板-安心验', 'bag_upload_anxinyan', 'category', 1, 'anxinyan', 1, 1, '{$now}', '{$now}'),
|
||||
(2, '箱包上传模板-中检', 'bag_upload_zhongjian', 'category', 1, 'zhongjian', 1, 1, '{$now}', '{$now}'),
|
||||
(3, '鞋类上传模板-安心验', 'shoe_upload_anxinyan', 'category', 2, 'anxinyan', 1, 1, '{$now}', '{$now}'),
|
||||
(4, '鞋类上传模板-中检', 'shoe_upload_zhongjian', 'category', 2, 'zhongjian', 1, 1, '{$now}', '{$now}'),
|
||||
(5, '腕表上传模板-安心验', 'watch_upload_anxinyan', 'category', 3, 'anxinyan', 1, 1, '{$now}', '{$now}'),
|
||||
(6, '腕表上传模板-中检', 'watch_upload_zhongjian', 'category', 3, 'zhongjian', 1, 1, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO upload_template_items (id, template_id, item_code, item_name, is_required, guide_text, sample_image_url, max_upload_count, sort_order, is_enabled, created_at, updated_at) VALUES
|
||||
(1, 1, 'overall_front', '商品整体图', 1, '请拍摄商品整体外观,确保主体完整入镜。', '', 1, 1, 1, '{$now}', '{$now}'),
|
||||
(2, 1, 'logo_detail', '品牌标识图', 1, '请拍摄 Logo 或标识位置,保证图像清晰无遮挡。', '', 1, 2, 1, '{$now}', '{$now}'),
|
||||
(3, 1, 'serial_label', '编码 / 标签图', 1, '请拍摄商品编码、标签或序列信息,确保内容可辨认。', '', 1, 3, 1, '{$now}', '{$now}'),
|
||||
(4, 1, 'hardware_detail', '做工细节图', 1, '请拍摄材质、走线、五金等关键细节位置。', '', 2, 4, 1, '{$now}', '{$now}'),
|
||||
(5, 1, 'purchase_voucher', '购买凭证', 0, '如有订单截图、发票或购物凭证,可一并上传。', '', 2, 5, 1, '{$now}', '{$now}'),
|
||||
(6, 1, 'special_detail', '特殊细节图', 0, '如存在疑问部位或瑕疵,可补充上传。', '', 2, 6, 1, '{$now}', '{$now}'),
|
||||
(7, 2, 'overall_front', '商品整体图', 1, '请拍摄商品整体外观,确保主体完整入镜。', '', 1, 1, 1, '{$now}', '{$now}'),
|
||||
(8, 2, 'logo_detail', '品牌标识图', 1, '请拍摄 Logo 或标识位置,保证图像清晰无遮挡。', '', 1, 2, 1, '{$now}', '{$now}'),
|
||||
(9, 2, 'serial_label', '编码 / 标签图', 1, '请拍摄商品编码、标签或序列信息,确保内容可辨认。', '', 1, 3, 1, '{$now}', '{$now}'),
|
||||
(10, 2, 'hardware_detail', '做工细节图', 1, '请拍摄材质、走线、五金等关键细节位置。', '', 2, 4, 1, '{$now}', '{$now}'),
|
||||
(11, 2, 'purchase_voucher', '购买凭证', 0, '如有订单截图、发票或购物凭证,可一并上传。', '', 2, 5, 1, '{$now}', '{$now}'),
|
||||
(12, 2, 'special_detail', '特殊细节图', 0, '如存在疑问部位或瑕疵,可补充上传。', '', 2, 6, 1, '{$now}', '{$now}'),
|
||||
(13, 3, 'overall_pair', '鞋款整体图', 1, '请拍摄左右脚整体外观,确保鞋面、鞋底完整入镜。', '', 2, 1, 1, '{$now}', '{$now}'),
|
||||
(14, 3, 'tongue_label', '鞋舌标签图', 1, '请拍摄鞋舌标签、尺码标及生产信息。', '', 2, 2, 1, '{$now}', '{$now}'),
|
||||
(15, 3, 'insole_detail', '鞋垫与内里图', 1, '请拍摄鞋垫、内里走线和内侧印刷细节。', '', 2, 3, 1, '{$now}', '{$now}'),
|
||||
(16, 3, 'sole_detail', '鞋底细节图', 1, '请补充鞋底纹路和磨损细节。', '', 2, 4, 1, '{$now}', '{$now}'),
|
||||
(17, 3, 'box_label', '鞋盒标签图', 0, '如有鞋盒,请拍摄鞋盒侧标信息。', '', 1, 5, 1, '{$now}', '{$now}'),
|
||||
(18, 3, 'purchase_voucher', '购买凭证', 0, '如有订单截图、小票或平台购买记录,可一并上传。', '', 2, 6, 1, '{$now}', '{$now}'),
|
||||
(19, 4, 'overall_pair', '鞋款整体图', 1, '请拍摄左右脚整体外观,确保鞋面、鞋底完整入镜。', '', 2, 1, 1, '{$now}', '{$now}'),
|
||||
(20, 4, 'tongue_label', '鞋舌标签图', 1, '请拍摄鞋舌标签、尺码标及生产信息。', '', 2, 2, 1, '{$now}', '{$now}'),
|
||||
(21, 4, 'insole_detail', '鞋垫与内里图', 1, '请拍摄鞋垫、内里走线和内侧印刷细节。', '', 2, 3, 1, '{$now}', '{$now}'),
|
||||
(22, 4, 'sole_detail', '鞋底细节图', 1, '请补充鞋底纹路和磨损细节。', '', 2, 4, 1, '{$now}', '{$now}'),
|
||||
(23, 4, 'box_label', '鞋盒标签图', 0, '如有鞋盒,请拍摄鞋盒侧标信息。', '', 1, 5, 1, '{$now}', '{$now}'),
|
||||
(24, 4, 'purchase_voucher', '购买凭证', 0, '如有订单截图、小票或平台购买记录,可一并上传。', '', 2, 6, 1, '{$now}', '{$now}'),
|
||||
(25, 5, 'overall_front', '腕表整体图', 1, '请拍摄表盘正面与整体外观。', '', 1, 1, 1, '{$now}', '{$now}'),
|
||||
(26, 5, 'back_case', '表背图', 1, '请拍摄表背刻字、结构和编码信息。', '', 1, 2, 1, '{$now}', '{$now}'),
|
||||
(27, 5, 'movement_detail', '机芯 / 内部结构图', 0, '如方便展示机芯,请补充内部结构图。', '', 2, 3, 1, '{$now}', '{$now}'),
|
||||
(28, 5, 'strap_buckle', '表带 / 表扣细节图', 1, '请拍摄表带材质、表扣刻字和连接处细节。', '', 2, 4, 1, '{$now}', '{$now}'),
|
||||
(29, 5, 'purchase_voucher', '购买凭证', 0, '如有保卡、发票或购买记录,可一并上传。', '', 2, 5, 1, '{$now}', '{$now}'),
|
||||
(30, 6, 'overall_front', '腕表整体图', 1, '请拍摄表盘正面与整体外观。', '', 1, 1, 1, '{$now}', '{$now}'),
|
||||
(31, 6, 'back_case', '表背图', 1, '请拍摄表背刻字、结构和编码信息。', '', 1, 2, 1, '{$now}', '{$now}'),
|
||||
(32, 6, 'movement_detail', '机芯 / 内部结构图', 0, '如方便展示机芯,请补充内部结构图。', '', 2, 3, 1, '{$now}', '{$now}'),
|
||||
(33, 6, 'strap_buckle', '表带 / 表扣细节图', 1, '请拍摄表带材质、表扣刻字和连接处细节。', '', 2, 4, 1, '{$now}', '{$now}'),
|
||||
(34, 6, 'purchase_voucher', '购买凭证', 0, '如有保卡、发票或购买记录,可一并上传。', '', 2, 5, 1, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO orders (id, order_no, appraisal_no, user_id, service_mode, service_provider, payment_status, order_status, display_status, estimated_finish_time, source_channel, source_customer_id, pay_amount, paid_at, created_at, updated_at) VALUES
|
||||
(1, 'AXY202604200001', 'AXY-APP-20260420-0001', 1, 'physical', 'zhongjian', 'paid', 'pending_supplement', '等待您补充资料', '2026-04-21 18:00:00', 'mini_program', '', 199.00, '2026-04-20 09:12:00', '2026-04-20 09:12:00', '{$now}'),
|
||||
(2, 'AXY202604190012', 'AXY-APP-20260419-0012', 1, 'physical', 'anxinyan', 'paid', 'in_first_review', '鉴定师处理中', '2026-04-20 20:00:00', 'h5', '', 99.00, '2026-04-19 13:02:00', '2026-04-19 13:02:00', '{$now}'),
|
||||
(3, 'AXY202604180088', 'AXY-APP-20260418-0088', 1, 'physical', 'zhongjian', 'paid', 'completed', '报告已出具', '2026-04-18 20:00:00', 'enterprise_push', 'ENT-DEMO-001', 199.00, '2026-04-18 08:18:00', '2026-04-18 08:18:00', '{$now}');
|
||||
|
||||
INSERT INTO order_products (id, order_id, category_id, category_name, brand_id, brand_name, color, size_spec, serial_no, product_name, product_cover, created_at, updated_at) VALUES
|
||||
(1, 1, 1, '奢侈品箱包', 1, 'Louis Vuitton', '老花', 'MM', '', 'Louis Vuitton 奢侈品箱包', '', '{$now}', '{$now}'),
|
||||
(2, 2, 2, '潮流鞋类', 2, 'Nike', 'Chicago', '42', '', 'Nike 潮流鞋类', '', '{$now}', '{$now}'),
|
||||
(3, 3, 3, '腕表', 3, 'Rolex', '银盘', '36mm', 'RX123456', 'Rolex 腕表', '', '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO order_extras (id, order_id, purchase_channel, purchase_price, purchase_date, usage_status, condition_desc, has_accessories, accessories_json, remark, created_at, updated_at) VALUES
|
||||
(1, 1, '专柜', 8500.00, '2026-03-01', 'light_use', '轻微使用痕迹', 1, JSON_ARRAY('防尘袋','包装盒'), '包身整体状态良好', '{$now}', '{$now}'),
|
||||
(2, 2, '官网', 1699.00, '2026-02-15', 'light_use', '鞋底轻微磨损', 1, JSON_ARRAY('鞋盒','购买凭证'), '补充鞋标与鞋盒标签', '{$now}', '{$now}'),
|
||||
(3, 3, '专柜', 52000.00, '2026-01-20', 'light_use', '整体状态良好', 1, JSON_ARRAY('表盒','保卡'), '用于正式报告展示', '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO order_shipping_targets (id, order_id, warehouse_id, warehouse_name, warehouse_code, service_provider, receiver_name, receiver_mobile, province, city, district, detail_address, service_time, notice, created_at, updated_at) VALUES
|
||||
(1, 1, 2, '中检合作鉴定中心', 'ZJ-WH-DEFAULT', 'zhongjian', '中检合作鉴定中心', '400-800-1314', '广东省', '深圳市', '南山区', '科技园鉴定路 88 号 安心验中检收件中心', '周一至周日 09:30-18:30', '中检鉴定订单请优先附上鉴定单号,寄出后尽快填写运单号。', '{$now}', '{$now}'),
|
||||
(2, 2, 1, '安心验鉴定中心', 'AXY-WH-DEFAULT', 'anxinyan', '安心验鉴定中心', '400-800-1314', '广东省', '深圳市', '南山区', '科技园鉴定路 88 号 安心验收件中心', '周一至周日 09:30-18:30', '寄送前请确认订单信息完整,包裹内附上订单号可提升签收后的处理效率。', '{$now}', '{$now}'),
|
||||
(3, 3, 2, '中检合作鉴定中心', 'ZJ-WH-DEFAULT', 'zhongjian', '中检合作鉴定中心', '400-800-1314', '广东省', '深圳市', '南山区', '科技园鉴定路 88 号 安心验中检收件中心', '周一至周日 09:30-18:30', '中检鉴定订单请优先附上鉴定单号,寄出后尽快填写运单号。', '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO order_timelines (id, order_id, node_code, node_text, node_desc, operator_type, operator_id, occurred_at, created_at) VALUES
|
||||
(1, 1, 'created', '下单成功', '订单已生成并完成支付', 'system', NULL, '2026-04-20 09:12:00', '{$now}'),
|
||||
(2, 1, 'submitted', '资料已提交', '用户已完成首轮资料上传', 'user', 1, '2026-04-20 09:30:00', '{$now}'),
|
||||
(3, 1, 'first_review', '鉴定中', '鉴定师正在进行专业判断', 'admin', 1, '2026-04-20 10:20:00', '{$now}'),
|
||||
(4, 1, 'supplement', '待补资料', '鉴定师需要补充编码近照与五金细节图', 'admin', 1, '2026-04-20 11:16:00', '{$now}'),
|
||||
(5, 2, 'created', '下单成功', '订单已生成并完成支付', 'system', NULL, '2026-04-19 13:02:00', '{$now}'),
|
||||
(6, 2, 'submitted', '资料已提交', '用户已完成首轮资料上传', 'user', 1, '2026-04-19 13:18:00', '{$now}'),
|
||||
(7, 2, 'received', '鉴定中心已收货', '商品已进入鉴定中心', 'system', NULL, '2026-04-19 15:10:00', '{$now}'),
|
||||
(8, 2, 'first_review', '鉴定中', '鉴定师正在处理,预计 24 小时内出具报告', 'admin', 2, '2026-04-20 09:10:00', '{$now}'),
|
||||
(9, 3, 'created', '下单成功', '订单已生成并完成支付', 'system', NULL, '2026-04-18 08:18:00', '{$now}'),
|
||||
(10, 3, 'received', '鉴定中心已收货', '商品已进入鉴定中心', 'system', NULL, '2026-04-18 10:12:00', '{$now}'),
|
||||
(11, 3, 'generating_report', '正在生成报告', '鉴定已完成,系统正在生成正式报告草稿', 'admin', 3, '2026-04-18 16:00:00', '{$now}'),
|
||||
(12, 3, 'report_published', '报告已出具', '正式报告已生成并可查看', 'system', NULL, '2026-04-18 18:26:00', '{$now}');
|
||||
|
||||
INSERT INTO order_supplement_tasks (id, order_id, reason, deadline, status, created_by, created_at, updated_at) VALUES
|
||||
(1, 1, '鉴定师需要补充编码近照与五金细节图,以继续完成判断。', '2026-04-21 18:00:00', 'pending', 1, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO order_supplement_task_items (id, task_id, item_code, item_name, guide_text, sample_image_url, is_required, created_at, updated_at) VALUES
|
||||
(1, 1, 'serial_label', '编码 / 标签图', '请补充清晰近照,确保编码内容完整可辨认。', '', 1, '{$now}', '{$now}'),
|
||||
(2, 1, 'hardware_detail', '五金细节图', '请避免反光与遮挡,完整拍摄边缘与刻印细节。', '', 1, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO appraisal_tasks (id, order_id, task_stage, service_provider, status, assignee_id, assignee_name, started_at, submitted_at, sla_deadline, is_overtime, created_at, updated_at) VALUES
|
||||
(1, 1, 'first_review', 'zhongjian', 'processing', 1, '张师傅', '2026-04-20 10:20:00', NULL, '2026-04-21 18:00:00', 0, '{$now}', '{$now}'),
|
||||
(2, 2, 'first_review', 'anxinyan', 'processing', 2, '李师傅', '2026-04-20 09:10:00', NULL, '2026-04-20 20:00:00', 0, '{$now}', '{$now}'),
|
||||
(3, 3, 'first_review', 'zhongjian', 'completed', 3, '王师傅', '2026-04-18 14:10:00', '2026-04-18 16:00:00', '2026-04-18 20:00:00', 0, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO appraisal_task_results (id, task_id, order_id, result_status, result_text, result_desc, condition_grade, condition_desc, valuation_min, valuation_max, valuation_desc, internal_remark, external_remark, created_at, updated_at) VALUES
|
||||
(1, 3, 3, 'authentic', '正品', '综合当前送检资料与商品特征判断,符合正品特征。', 'A', '整体状态良好,存在轻微使用痕迹。', 2800.00, 3200.00, '当前估值仅供参考,具体以市场流通情况为准。', '鉴定完成,可出正式报告。', '综合当前送检资料与商品特征判断,符合正品特征。', '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO reports (id, report_no, order_id, appraisal_no, report_type, service_provider, institution_name, report_title, report_status, report_version, publish_time, created_at, updated_at) VALUES
|
||||
(1, 'AXY-R-20260420-0001', 3, 'AXY-APP-20260418-0088', 'appraisal', 'zhongjian', '中检合作机构', '中检鉴定报告', 'published', 1, '2026-04-18 18:26:00', '{$now}', '{$now}');
|
||||
");
|
||||
|
||||
$productSnapshot = json_encode([
|
||||
'product_name' => 'Rolex 腕表',
|
||||
'category_name' => '腕表',
|
||||
'brand_name' => 'Rolex',
|
||||
'color' => '银盘',
|
||||
'size_spec' => '36mm',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$resultSnapshot = json_encode([
|
||||
'result_status' => 'authentic',
|
||||
'result_text' => '正品',
|
||||
'result_desc' => '综合当前送检资料与商品特征判断,符合正品特征。',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$appraisalSnapshot = json_encode([
|
||||
'service_provider' => 'zhongjian',
|
||||
'institution_name' => '中检合作机构',
|
||||
'appraiser_name' => '张师傅',
|
||||
'reviewer_name' => '张师傅',
|
||||
'appraisal_time' => '2026-04-18 16:00:00',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$valuationSnapshot = json_encode([
|
||||
'condition_grade' => 'A',
|
||||
'condition_desc' => '整体状态良好,存在轻微使用痕迹。',
|
||||
'valuation_min' => 2800,
|
||||
'valuation_max' => 3200,
|
||||
'valuation_desc' => '当前估值仅供参考,具体以市场流通情况为准。',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$stmt = $pdo->prepare('INSERT INTO report_contents (id, report_id, product_snapshot_json, result_snapshot_json, appraisal_snapshot_json, valuation_snapshot_json, risk_notice_text, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||
$stmt->execute([
|
||||
1,
|
||||
1,
|
||||
$productSnapshot,
|
||||
$resultSnapshot,
|
||||
$appraisalSnapshot,
|
||||
$valuationSnapshot,
|
||||
'本报告基于送检商品及当前提交资料出具。若商品状态或所附资料发生变化,报告结论可能不再适用。',
|
||||
$now,
|
||||
$now,
|
||||
]);
|
||||
|
||||
$stmt = $pdo->prepare('INSERT INTO report_verifies (id, report_id, report_no, verify_token, verify_qrcode_url, verify_url, verify_status, verify_count, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||
$stmt->execute([
|
||||
1,
|
||||
1,
|
||||
'AXY-R-20260420-0001',
|
||||
'verify_axyr202604200001',
|
||||
'',
|
||||
'/api/app/verify?report_no=AXY-R-20260420-0001',
|
||||
'valid',
|
||||
0,
|
||||
$now,
|
||||
$now,
|
||||
]);
|
||||
|
||||
$pdo->exec("
|
||||
INSERT INTO message_templates (id, template_name, template_code, channel, event_code, title, content, is_enabled, created_at, updated_at) VALUES
|
||||
(1, '下单成功通知', 'order_created_inbox', 'inbox', 'order_created', '订单提交成功', '您的鉴定订单已提交成功,可前往订单中心查看进度。', 1, '{$now}', '{$now}'),
|
||||
(2, '待补资料通知', 'supplement_required_inbox', 'inbox', 'supplement_required', '请补充鉴定资料', '鉴定师需要您补充资料后继续处理,请尽快进入订单详情查看。', 1, '{$now}', '{$now}'),
|
||||
(3, '报告已出具通知', 'report_published_inbox', 'inbox', 'report_published', '报告已出具', '您的正式报告已生成,可前往报告中心查看并完成验真。', 1, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO message_rules (id, event_code, channel, template_id, delay_seconds, dedupe_window, is_enabled, created_at, updated_at) VALUES
|
||||
(1, 'order_created', 'inbox', 1, 0, 0, 1, '{$now}', '{$now}'),
|
||||
(2, 'supplement_required', 'inbox', 2, 0, 0, 1, '{$now}', '{$now}'),
|
||||
(3, 'report_published', 'inbox', 3, 0, 0, 1, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO message_logs (id, user_id, template_id, biz_type, biz_id, channel, status, fail_reason, sent_at, created_at, updated_at) VALUES
|
||||
(1, 1, 1, 'order', 1, 'inbox', 'sent', '', '2026-04-20 09:12:00', '{$now}', '{$now}'),
|
||||
(2, 1, 2, 'order', 1, 'inbox', 'sent', '', '2026-04-20 11:16:00', '{$now}', '{$now}'),
|
||||
(3, 1, 3, 'report', 1, 'inbox', 'pending', '', NULL, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO user_messages (id, user_id, title, content, biz_type, biz_id, is_read, read_at, created_at, updated_at) VALUES
|
||||
(1, 1, '订单提交成功', '您的鉴定订单已提交成功,可前往订单中心查看进度。', 'order', 1, 1, '2026-04-20 09:20:00', '{$now}', '{$now}'),
|
||||
(2, 1, '请补充鉴定资料', '鉴定师需要您补充资料后继续处理,请尽快进入订单详情查看。', 'order', 1, 0, NULL, '{$now}', '{$now}'),
|
||||
(3, 1, '报告已出具', '您的正式报告已生成,可前往报告中心查看并完成验真。', 'report', 1, 0, NULL, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO tickets (id, ticket_no, ticket_type, biz_type, biz_id, order_id, user_id, status, priority, assignee_id, title, content, closed_at, created_at, updated_at) VALUES
|
||||
(1, 'TK202604200001', 'upload_issue', 'order', 1, 1, 1, 'processing', 'high', 1, '补图说明咨询', '用户反馈不确定编码标签该如何拍摄,希望客服提供拍摄建议。', NULL, '{$now}', '{$now}'),
|
||||
(2, 'TK202604200002', 'report_issue', 'report', 1, 3, 1, 'pending', 'normal', NULL, '报告内容咨询', '用户希望了解估值说明与评级口径。', NULL, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO ticket_messages (id, ticket_id, sender_type, sender_id, content, attachments_json, created_at) VALUES
|
||||
(1, 1, 'user', 1, '我不确定编码标签应该怎么拍,担心影响鉴定结果。', NULL, '2026-04-20 11:18:00'),
|
||||
(2, 1, 'customer_service', 1, '您好,请优先拍摄标签整体区域,再补一张放大近照,保证编码内容完整可辨认。', NULL, '2026-04-20 11:25:00'),
|
||||
(3, 2, 'user', 1, '请问 A 级和估值区间的口径是什么?', NULL, '2026-04-20 12:10:00'),
|
||||
(4, 2, 'system', NULL, '工单已创建,等待客服跟进。', NULL, '2026-04-20 12:11:00');
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
INSERT INTO admin_users (id, name, mobile, email, password, status, last_login_at, created_at, updated_at) VALUES
|
||||
(1, '系统管理员', '13800138000', 'admin@anxinyan.local', '{$adminPasswordHash}', 'enabled', NULL, '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO admin_roles (id, name, code, status, created_at, updated_at) VALUES
|
||||
(1, '超级管理员', 'super_admin', 'enabled', '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO admin_role_relations (id, admin_user_id, role_id, created_at) VALUES
|
||||
(1, 1, 1, '{$now}');
|
||||
|
||||
INSERT INTO admin_permissions (id, name, code, module, action, created_at, updated_at) VALUES
|
||||
(1, '查看工作台', 'dashboard.view', 'dashboard', 'view', '{$now}', '{$now}'),
|
||||
(2, '管理订单', 'orders.manage', 'orders', 'manage', '{$now}', '{$now}'),
|
||||
(3, '管理鉴定任务', 'appraisal_tasks.manage', 'appraisal_tasks', 'manage', '{$now}', '{$now}'),
|
||||
(4, '管理商品资料', 'catalog.manage', 'catalog', 'manage', '{$now}', '{$now}'),
|
||||
(5, '管理报告', 'reports.manage', 'reports', 'manage', '{$now}', '{$now}'),
|
||||
(6, '管理消息', 'messages.manage', 'messages', 'manage', '{$now}', '{$now}'),
|
||||
(7, '管理工单', 'tickets.manage', 'tickets', 'manage', '{$now}', '{$now}'),
|
||||
(8, '管理用户', 'users.manage', 'users', 'manage', '{$now}', '{$now}'),
|
||||
(9, '管理客户', 'customers.manage', 'customers', 'manage', '{$now}', '{$now}'),
|
||||
(10, '管理仓库', 'warehouses.manage', 'warehouses', 'manage', '{$now}', '{$now}'),
|
||||
(11, '管理物料', 'materials.manage', 'materials', 'manage', '{$now}', '{$now}'),
|
||||
(12, '管理权限', 'access.manage', 'access', 'manage', '{$now}', '{$now}'),
|
||||
(13, '管理系统配置', 'system.manage', 'system_config', 'manage', '{$now}', '{$now}');
|
||||
|
||||
INSERT INTO admin_role_permissions (id, role_id, permission_id, created_at) VALUES
|
||||
(1, 1, 1, '{$now}'),
|
||||
(2, 1, 2, '{$now}'),
|
||||
(3, 1, 3, '{$now}'),
|
||||
(4, 1, 4, '{$now}'),
|
||||
(5, 1, 5, '{$now}'),
|
||||
(6, 1, 6, '{$now}'),
|
||||
(7, 1, 7, '{$now}'),
|
||||
(8, 1, 8, '{$now}'),
|
||||
(9, 1, 9, '{$now}'),
|
||||
(10, 1, 10, '{$now}'),
|
||||
(11, 1, 11, '{$now}'),
|
||||
(12, 1, 12, '{$now}'),
|
||||
(13, 1, 13, '{$now}');
|
||||
|
||||
INSERT INTO system_configs (id, config_group, config_key, config_value, remark, created_at, updated_at) VALUES
|
||||
(1, 'mini_program', 'app_id', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(2, 'mini_program', 'app_secret', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(3, 'mini_program', 'original_id', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(4, 'h5', 'app_id', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(5, 'h5', 'app_secret', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(6, 'h5', 'oauth_redirect_url', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(7, 'h5', 'page_base_url', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(8, 'payment', 'mch_id', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(9, 'payment', 'api_v3_key', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(10, 'payment', 'merchant_serial_no', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(11, 'payment', 'merchant_private_key', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(12, 'payment', 'platform_certificate_serial', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(13, 'payment', 'notify_url', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(14, 'sms', 'access_key_id', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(15, 'sms', 'access_key_secret', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(16, 'sms', 'sign_name', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(17, 'sms', 'login_template_code', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(18, 'sms', 'region_id', 'cn-hangzhou', '后台系统配置', '{$now}', '{$now}'),
|
||||
(19, 'sms', 'endpoint', '', '后台系统配置', '{$now}', '{$now}'),
|
||||
(20, 'user_settings', 'user_1', '{\"notify_order\":true,\"notify_report\":true,\"notify_supplement\":true,\"notify_ticket\":true,\"marketing_notify\":false,\"privacy_mode\":false}', '用户端设置偏好', '{$now}', '{$now}');
|
||||
");
|
||||
|
||||
echo "SEED_OK\n";
|
||||
15
server-api/tools/debug_verify.php
Normal file
15
server-api/tools/debug_verify.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$config = array_replace_recursive(config('thinkorm', []), config('think-orm', []));
|
||||
support\think\Db::setConfig($config);
|
||||
|
||||
$content = support\think\Db::name('report_contents')->where('report_id', 1)->find();
|
||||
|
||||
var_dump($content);
|
||||
193
server-api/tools/release_audit.php
Normal file
193
server-api/tools/release_audit.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$projectRoot = dirname(__DIR__, 2);
|
||||
$issues = [];
|
||||
|
||||
function add_issue(array &$issues, string $level, string $title, string $detail): void
|
||||
{
|
||||
$issues[] = compact('level', 'title', 'detail');
|
||||
}
|
||||
|
||||
function check(bool $condition, array &$issues, string $failLevel, string $title, string $detail): void
|
||||
{
|
||||
if (!$condition) {
|
||||
add_issue($issues, $failLevel, $title, $detail);
|
||||
}
|
||||
}
|
||||
|
||||
function parseJsonFile(string $path): ?array
|
||||
{
|
||||
$content = @file_get_contents($path);
|
||||
if ($content === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$content = preg_replace('/^\xEF\xBB\xBF/', '', $content);
|
||||
$content = preg_replace('!/\*.*?\*/!s', '', $content);
|
||||
$content = preg_replace('/^\s*\/\/.*$/m', '', $content);
|
||||
|
||||
$decoded = json_decode((string)$content, true);
|
||||
return is_array($decoded) ? $decoded : null;
|
||||
}
|
||||
|
||||
function isPlaceholderApiBase(string $apiBase): bool
|
||||
{
|
||||
if ($apiBase === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$normalized = strtolower($apiBase);
|
||||
if (str_contains($normalized, '127.0.0.1') || str_contains($normalized, 'localhost')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return str_contains($normalized, 'example.com');
|
||||
}
|
||||
|
||||
$env = $_ENV;
|
||||
check(($env['APP_ENV'] ?? '') === 'production', $issues, 'FAIL', 'APP_ENV 非 production', '当前 .env 中 APP_ENV 不是 production。');
|
||||
check(in_array(strtolower((string)($env['APP_DEBUG'] ?? '')), ['false', '0'], true), $issues, 'FAIL', 'APP_DEBUG 未关闭', '当前 .env 中 APP_DEBUG 仍然开启。');
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$env['DB_HOST'] ?? '127.0.0.1',
|
||||
$env['DB_PORT'] ?? '3306',
|
||||
$env['DB_DATABASE'] ?? '',
|
||||
$env['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$env['DB_USERNAME'] ?? '',
|
||||
$env['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
$configRows = $pdo->query("SELECT config_group, config_key, config_value FROM system_configs")->fetchAll();
|
||||
$configMap = [];
|
||||
foreach ($configRows as $row) {
|
||||
$configMap[$row['config_group'] . '.' . $row['config_key']] = (string)($row['config_value'] ?? '');
|
||||
}
|
||||
|
||||
$requiredConfigKeys = [
|
||||
'mini_program.app_id',
|
||||
'mini_program.app_secret',
|
||||
'mini_program.original_id',
|
||||
'h5.app_id',
|
||||
'h5.app_secret',
|
||||
'h5.oauth_redirect_url',
|
||||
'h5.page_base_url',
|
||||
'sms.access_key_id',
|
||||
'sms.access_key_secret',
|
||||
'sms.sign_name',
|
||||
'sms.login_template_code',
|
||||
'payment.mch_id',
|
||||
'payment.api_v3_key',
|
||||
'payment.merchant_serial_no',
|
||||
'payment.merchant_private_key',
|
||||
'payment.platform_certificate_serial',
|
||||
'payment.notify_url',
|
||||
];
|
||||
|
||||
foreach ($requiredConfigKeys as $key) {
|
||||
check(($configMap[$key] ?? '') !== '', $issues, 'FAIL', "系统配置缺失: {$key}", "后台系统配置中 {$key} 仍为空。");
|
||||
}
|
||||
|
||||
if (($configMap['h5.page_base_url'] ?? '') !== '' && isPlaceholderApiBase((string)$configMap['h5.page_base_url'])) {
|
||||
add_issue($issues, 'FAIL', 'H5 页面根地址未配置正式域名', '后台系统配置 h5.page_base_url 仍为本地或占位地址,扫码公开链接将无法用于正式环境。');
|
||||
}
|
||||
|
||||
$demoValues = [
|
||||
'mini_program.app_id' => 'wx1234567890test',
|
||||
'h5.app_id' => 'h5_app_demo',
|
||||
'payment.mch_id' => '1900000109',
|
||||
'payment.api_v3_key' => 'demo_api_v3_key_1234567890',
|
||||
];
|
||||
|
||||
foreach ($demoValues as $key => $value) {
|
||||
check(($configMap[$key] ?? '') !== $value, $issues, 'FAIL', "系统配置仍是演示值: {$key}", "后台系统配置 {$key} 仍为演示值 {$value}。");
|
||||
}
|
||||
|
||||
$admins = $pdo->query("SELECT id, mobile, password, status FROM admin_users ORDER BY id ASC")->fetchAll();
|
||||
$hasDefaultAdmin = false;
|
||||
$hasViewerAdmin = false;
|
||||
foreach ($admins as $admin) {
|
||||
if (($admin['mobile'] ?? '') === '13800138000') {
|
||||
$hasDefaultAdmin = true;
|
||||
if (password_verify('Admin@123456', (string)$admin['password'])) {
|
||||
add_issue($issues, 'FAIL', '默认超级管理员密码未修改', '管理员 13800138000 仍可用默认密码 Admin@123456 登录。');
|
||||
}
|
||||
}
|
||||
if (($admin['mobile'] ?? '') === '13800138001' && ($admin['status'] ?? '') === 'enabled') {
|
||||
$hasViewerAdmin = true;
|
||||
}
|
||||
}
|
||||
if ($hasViewerAdmin) {
|
||||
add_issue($issues, 'WARN', '测试管理员仍启用', '测试管理员 13800138001 / Test@123456 仍处于启用状态。');
|
||||
}
|
||||
if (!$hasDefaultAdmin) {
|
||||
add_issue($issues, 'WARN', '未检测到默认超级管理员', '未找到手机号 13800138000 的默认超级管理员账号。');
|
||||
}
|
||||
|
||||
$user = $pdo->query("SELECT nickname FROM users WHERE id = 1")->fetch();
|
||||
if ($user && str_contains((string)$user['nickname'], '测试')) {
|
||||
add_issue($issues, 'WARN', '测试昵称未清理', '用户昵称仍包含“测试”字样。');
|
||||
}
|
||||
|
||||
$manifestPath = $projectRoot . '/user-app/src/manifest.json';
|
||||
$manifest = parseJsonFile($manifestPath);
|
||||
if (!is_array($manifest)) {
|
||||
add_issue($issues, 'FAIL', 'manifest.json 解析失败', '无法解析 user-app/src/manifest.json。');
|
||||
} else {
|
||||
check(($manifest['mp-weixin']['appid'] ?? '') !== '', $issues, 'FAIL', '小程序 manifest appid 为空', 'manifest.json 中 mp-weixin.appid 仍为空。');
|
||||
if (($configMap['mini_program.app_id'] ?? '') !== '') {
|
||||
check(
|
||||
($manifest['mp-weixin']['appid'] ?? '') === $configMap['mini_program.app_id'],
|
||||
$issues,
|
||||
'FAIL',
|
||||
'小程序 manifest appid 未同步后台配置',
|
||||
'manifest.json 中 mp-weixin.appid 与后台系统配置 mini_program.app_id 不一致,请先执行配置同步。'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$adminProdEnvPath = $projectRoot . '/admin-web/.env.production';
|
||||
$userProdEnvPath = $projectRoot . '/user-app/.env.production';
|
||||
$adminProdEnv = @parse_ini_file($adminProdEnvPath);
|
||||
$userProdEnv = @parse_ini_file($userProdEnvPath);
|
||||
if (is_array($adminProdEnv)) {
|
||||
$adminApiBase = (string)($adminProdEnv['VITE_API_BASE_URL'] ?? '');
|
||||
if (isPlaceholderApiBase($adminApiBase)) {
|
||||
add_issue($issues, 'FAIL', 'admin-web 生产 API 未配置正式域名', 'admin-web/.env.production 的 VITE_API_BASE_URL 仍为本地或占位地址。');
|
||||
}
|
||||
} else {
|
||||
add_issue($issues, 'FAIL', 'admin-web 缺少生产环境变量', '无法解析 admin-web/.env.production。');
|
||||
}
|
||||
if (is_array($userProdEnv)) {
|
||||
$userApiBase = (string)($userProdEnv['VITE_API_BASE_URL'] ?? '');
|
||||
if (isPlaceholderApiBase($userApiBase)) {
|
||||
add_issue($issues, 'FAIL', 'user-app 生产 API 未配置正式域名', 'user-app/.env.production 的 VITE_API_BASE_URL 仍为本地或占位地址。');
|
||||
}
|
||||
} else {
|
||||
add_issue($issues, 'FAIL', 'user-app 缺少生产环境变量', '无法解析 user-app/.env.production。');
|
||||
}
|
||||
|
||||
if (!$issues) {
|
||||
echo "RELEASE_AUDIT_OK\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
echo "RELEASE_AUDIT_ISSUES\n";
|
||||
foreach ($issues as $item) {
|
||||
echo "[{$item['level']}] {$item['title']} - {$item['detail']}\n";
|
||||
}
|
||||
32
server-api/tools/schema_upgrade_appraisal_evidence.php
Normal file
32
server-api/tools/schema_upgrade_appraisal_evidence.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
$env = parse_ini_file(__DIR__ . '/../.env');
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
|
||||
$env['DB_HOST'] ?? '127.0.0.1',
|
||||
$env['DB_PORT'] ?? '3306',
|
||||
$env['DB_DATABASE'] ?? 'anxinyan'
|
||||
);
|
||||
|
||||
$pdo = new PDO($dsn, $env['DB_USERNAME'] ?? 'root', $env['DB_PASSWORD'] ?? '', [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
]);
|
||||
|
||||
function hasColumn(PDO $pdo, string $table, string $column): bool
|
||||
{
|
||||
$stmt = $pdo->prepare("SHOW COLUMNS FROM `{$table}` LIKE ?");
|
||||
$stmt->execute([$column]);
|
||||
return (bool)$stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
if (!hasColumn($pdo, 'appraisal_task_results', 'attachments_json')) {
|
||||
$pdo->exec("ALTER TABLE appraisal_task_results ADD COLUMN attachments_json JSON NULL AFTER valuation_desc");
|
||||
echo "ADD_COLUMN appraisal_task_results.attachments_json\n";
|
||||
}
|
||||
|
||||
if (!hasColumn($pdo, 'report_contents', 'evidence_attachments_json')) {
|
||||
$pdo->exec("ALTER TABLE report_contents ADD COLUMN evidence_attachments_json JSON NULL AFTER valuation_snapshot_json");
|
||||
echo "ADD_COLUMN report_contents.evidence_attachments_json\n";
|
||||
}
|
||||
|
||||
echo "done\n";
|
||||
196
server-api/tools/schema_upgrade_enterprise_customers.php
Normal file
196
server-api/tools/schema_upgrade_enterprise_customers.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
function hasTable(PDO $pdo, string $table): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?');
|
||||
$stmt->execute([$table]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
function hasPermission(PDO $pdo, string $code): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM admin_permissions WHERE code = ?');
|
||||
$stmt->execute([$code]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'enterprise_customers')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE enterprise_customers (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
customer_code VARCHAR(64) NOT NULL,
|
||||
customer_name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
contact_name VARCHAR(64) NOT NULL DEFAULT '',
|
||||
contact_mobile VARCHAR(32) NOT NULL DEFAULT '',
|
||||
contact_email VARCHAR(128) NOT NULL DEFAULT '',
|
||||
settlement_type VARCHAR(32) NOT NULL DEFAULT 'monthly',
|
||||
user_id BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||
webhook_url VARCHAR(500) NOT NULL DEFAULT '',
|
||||
webhook_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'enabled',
|
||||
remark VARCHAR(500) NOT NULL DEFAULT '',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_enterprise_customers_code (customer_code),
|
||||
KEY idx_enterprise_customers_status (status),
|
||||
KEY idx_enterprise_customers_user_id (user_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='大客户资料'
|
||||
SQL);
|
||||
echo "CREATE_TABLE enterprise_customers\n";
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'enterprise_customer_apps')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE enterprise_customer_apps (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
customer_id BIGINT UNSIGNED NOT NULL,
|
||||
app_name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
app_key VARCHAR(64) NOT NULL,
|
||||
app_secret_cipher TEXT NULL,
|
||||
secret_last4 VARCHAR(8) NOT NULL DEFAULT '',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'enabled',
|
||||
last_used_at DATETIME NULL DEFAULT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_enterprise_customer_apps_key (app_key),
|
||||
KEY idx_enterprise_customer_apps_customer_id (customer_id),
|
||||
KEY idx_enterprise_customer_apps_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='大客户开放接口应用'
|
||||
SQL);
|
||||
echo "CREATE_TABLE enterprise_customer_apps\n";
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'enterprise_api_nonces')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE enterprise_api_nonces (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
app_key VARCHAR(64) NOT NULL,
|
||||
nonce VARCHAR(128) NOT NULL,
|
||||
request_timestamp BIGINT NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_enterprise_api_nonces_key_nonce (app_key, nonce),
|
||||
KEY idx_enterprise_api_nonces_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='开放接口防重放Nonce'
|
||||
SQL);
|
||||
echo "CREATE_TABLE enterprise_api_nonces\n";
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'enterprise_customer_order_refs')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE enterprise_customer_order_refs (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
customer_id BIGINT UNSIGNED NOT NULL,
|
||||
external_order_no VARCHAR(128) NOT NULL,
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
order_no VARCHAR(64) NOT NULL DEFAULT '',
|
||||
appraisal_no VARCHAR(64) NOT NULL DEFAULT '',
|
||||
payload_hash VARCHAR(64) NOT NULL DEFAULT '',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_enterprise_customer_order_refs_external (customer_id, external_order_no),
|
||||
UNIQUE KEY uk_enterprise_customer_order_refs_order_id (order_id),
|
||||
KEY idx_enterprise_customer_order_refs_order_no (order_no)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='大客户外部订单映射'
|
||||
SQL);
|
||||
echo "CREATE_TABLE enterprise_customer_order_refs\n";
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'enterprise_order_events')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE enterprise_order_events (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
customer_id BIGINT UNSIGNED NOT NULL,
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
external_order_no VARCHAR(128) NOT NULL DEFAULT '',
|
||||
event_code VARCHAR(64) NOT NULL,
|
||||
event_text VARCHAR(128) NOT NULL DEFAULT '',
|
||||
status_code VARCHAR(64) NOT NULL DEFAULT '',
|
||||
status_text VARCHAR(128) NOT NULL DEFAULT '',
|
||||
payload_json JSON NULL,
|
||||
occurred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_enterprise_order_events_customer_id (customer_id),
|
||||
KEY idx_enterprise_order_events_order_id (order_id),
|
||||
KEY idx_enterprise_order_events_event_code (event_code),
|
||||
KEY idx_enterprise_order_events_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='大客户订单事件'
|
||||
SQL);
|
||||
echo "CREATE_TABLE enterprise_order_events\n";
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'enterprise_webhook_deliveries')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE enterprise_webhook_deliveries (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
event_id BIGINT UNSIGNED NOT NULL,
|
||||
customer_id BIGINT UNSIGNED NOT NULL,
|
||||
webhook_url VARCHAR(500) NOT NULL DEFAULT '',
|
||||
app_key VARCHAR(64) NOT NULL DEFAULT '',
|
||||
attempt_no INT NOT NULL DEFAULT 1,
|
||||
delivery_status VARCHAR(32) NOT NULL DEFAULT 'pending',
|
||||
http_status INT NOT NULL DEFAULT 0,
|
||||
response_body TEXT NULL,
|
||||
error_message VARCHAR(500) NOT NULL DEFAULT '',
|
||||
is_manual TINYINT(1) NOT NULL DEFAULT 0,
|
||||
sent_at DATETIME NULL DEFAULT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_enterprise_webhook_deliveries_event_id (event_id),
|
||||
KEY idx_enterprise_webhook_deliveries_customer_id (customer_id),
|
||||
KEY idx_enterprise_webhook_deliveries_status (delivery_status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='大客户Webhook推送记录'
|
||||
SQL);
|
||||
echo "CREATE_TABLE enterprise_webhook_deliveries\n";
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if (!hasPermission($pdo, 'customers.manage')) {
|
||||
$stmt = $pdo->prepare('INSERT INTO admin_permissions (name, code, module, action, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)');
|
||||
$stmt->execute(['管理客户', 'customers.manage', 'customers', 'manage', $now, $now]);
|
||||
echo "ADD_PERMISSION customers.manage\n";
|
||||
}
|
||||
|
||||
$permissionId = (int)$pdo->query("SELECT id FROM admin_permissions WHERE code = 'customers.manage'")->fetchColumn();
|
||||
$superRoleId = (int)$pdo->query("SELECT id FROM admin_roles WHERE code = 'super_admin'")->fetchColumn();
|
||||
if ($permissionId > 0 && $superRoleId > 0) {
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM admin_role_permissions WHERE role_id = ? AND permission_id = ?');
|
||||
$stmt->execute([$superRoleId, $permissionId]);
|
||||
if ((int)$stmt->fetchColumn() === 0) {
|
||||
$insert = $pdo->prepare('INSERT INTO admin_role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)');
|
||||
$insert->execute([$superRoleId, $permissionId, $now]);
|
||||
echo "ADD_SUPER_ADMIN_PERMISSION customers.manage\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "SCHEMA_UPGRADE_OK\n";
|
||||
69
server-api/tools/schema_upgrade_manual_reports.php
Normal file
69
server-api/tools/schema_upgrade_manual_reports.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
function hasColumn(PDO $pdo, string $table, string $column): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?');
|
||||
$stmt->execute([$table, $column]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
function hasIndex(PDO $pdo, string $table, string $index): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ?');
|
||||
$stmt->execute([$table, $index]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
function hasSystemConfig(PDO $pdo, string $group, string $key): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM system_configs WHERE config_group = ? AND config_key = ?');
|
||||
$stmt->execute([$group, $key]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
if (!hasColumn($pdo, 'reports', 'report_type')) {
|
||||
$pdo->exec("ALTER TABLE reports ADD COLUMN report_type VARCHAR(32) NOT NULL DEFAULT 'appraisal' AFTER appraisal_no");
|
||||
echo "ADD_COLUMN reports.report_type\n";
|
||||
}
|
||||
|
||||
if (!hasIndex($pdo, 'reports', 'idx_reports_report_type')) {
|
||||
$pdo->exec("ALTER TABLE reports ADD KEY idx_reports_report_type (report_type)");
|
||||
echo "ADD_INDEX reports.idx_reports_report_type\n";
|
||||
}
|
||||
|
||||
$pdo->exec("UPDATE reports SET report_type = 'appraisal' WHERE report_type = '' OR report_type IS NULL");
|
||||
|
||||
if (!hasSystemConfig($pdo, 'h5', 'page_base_url')) {
|
||||
$stmt = $pdo->prepare('INSERT INTO system_configs (config_group, config_key, config_value, remark, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)');
|
||||
$stmt->execute(['h5', 'page_base_url', '', '后台系统配置', $now, $now]);
|
||||
echo "ADD_CONFIG h5.page_base_url\n";
|
||||
}
|
||||
|
||||
echo "SCHEMA_UPGRADE_OK\n";
|
||||
181
server-api/tools/schema_upgrade_materials.php
Normal file
181
server-api/tools/schema_upgrade_materials.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
function hasTable(PDO $pdo, string $table): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?');
|
||||
$stmt->execute([$table]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
function hasPermission(PDO $pdo, string $code): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM admin_permissions WHERE code = ?');
|
||||
$stmt->execute([$code]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'material_batches')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE material_batches (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
batch_no VARCHAR(64) NOT NULL,
|
||||
total_count INT NOT NULL DEFAULT 0,
|
||||
remark VARCHAR(500) NOT NULL DEFAULT '',
|
||||
download_count INT NOT NULL DEFAULT 0,
|
||||
last_downloaded_at DATETIME NULL DEFAULT NULL,
|
||||
created_by BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||
created_by_name VARCHAR(64) NOT NULL DEFAULT '',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_material_batches_batch_no (batch_no),
|
||||
KEY idx_material_batches_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物料二维码批次'
|
||||
SQL);
|
||||
echo "CREATE_TABLE material_batches\n";
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'material_tag_codes')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE material_tag_codes (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
batch_id BIGINT UNSIGNED NOT NULL,
|
||||
qr_token VARCHAR(80) NOT NULL,
|
||||
qr_url VARCHAR(500) NOT NULL,
|
||||
verify_code VARCHAR(16) NOT NULL,
|
||||
bind_status VARCHAR(32) NOT NULL DEFAULT 'unbound',
|
||||
report_id BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||
report_no VARCHAR(64) NOT NULL DEFAULT '',
|
||||
bound_task_id BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||
bound_order_id BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||
bound_by BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||
bound_by_name VARCHAR(64) NOT NULL DEFAULT '',
|
||||
bound_at DATETIME NULL DEFAULT NULL,
|
||||
scan_count INT NOT NULL DEFAULT 0,
|
||||
verify_count INT NOT NULL DEFAULT 0,
|
||||
last_scanned_at DATETIME NULL DEFAULT NULL,
|
||||
last_verified_at DATETIME NULL DEFAULT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_material_tag_codes_qr_token (qr_token),
|
||||
UNIQUE KEY uk_material_tag_codes_qr_url (qr_url),
|
||||
UNIQUE KEY uk_material_tag_codes_report_id (report_id),
|
||||
KEY idx_material_tag_codes_batch_id (batch_id),
|
||||
KEY idx_material_tag_codes_verify_code (verify_code),
|
||||
KEY idx_material_tag_codes_report_no (report_no),
|
||||
KEY idx_material_tag_codes_bind_status (bind_status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物料吊牌二维码'
|
||||
SQL);
|
||||
echo "CREATE_TABLE material_tag_codes\n";
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'material_batch_download_logs')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE material_batch_download_logs (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
batch_id BIGINT UNSIGNED NOT NULL,
|
||||
operator_id BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||
operator_name VARCHAR(64) NOT NULL DEFAULT '',
|
||||
ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
user_agent VARCHAR(500) NOT NULL DEFAULT '',
|
||||
downloaded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_material_batch_download_logs_batch_id (batch_id),
|
||||
KEY idx_material_batch_download_logs_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物料批次下载日志'
|
||||
SQL);
|
||||
echo "CREATE_TABLE material_batch_download_logs\n";
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'material_tag_scan_logs')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE material_tag_scan_logs (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
tag_code_id BIGINT UNSIGNED NOT NULL,
|
||||
batch_id BIGINT UNSIGNED NOT NULL,
|
||||
report_id BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||
report_no VARCHAR(64) NOT NULL DEFAULT '',
|
||||
verify_type VARCHAR(32) NOT NULL DEFAULT 'scan',
|
||||
verify_code_input VARCHAR(16) NOT NULL DEFAULT '',
|
||||
verify_passed TINYINT(1) NOT NULL DEFAULT 0,
|
||||
ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
user_agent VARCHAR(500) NOT NULL DEFAULT '',
|
||||
scanned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_material_tag_scan_logs_tag_code_id (tag_code_id),
|
||||
KEY idx_material_tag_scan_logs_batch_id (batch_id),
|
||||
KEY idx_material_tag_scan_logs_report_id (report_id),
|
||||
KEY idx_material_tag_scan_logs_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物料吊牌扫码与验真日志'
|
||||
SQL);
|
||||
echo "CREATE_TABLE material_tag_scan_logs\n";
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if (!hasPermission($pdo, 'materials.manage')) {
|
||||
$stmt = $pdo->prepare('INSERT INTO admin_permissions (name, code, module, action, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)');
|
||||
$stmt->execute(['管理物料', 'materials.manage', 'materials', 'manage', $now, $now]);
|
||||
echo "ADD_PERMISSION materials.manage\n";
|
||||
}
|
||||
|
||||
$permissionId = (int)$pdo->query("SELECT id FROM admin_permissions WHERE code = 'materials.manage'")->fetchColumn();
|
||||
$superRoleId = (int)$pdo->query("SELECT id FROM admin_roles WHERE code = 'super_admin'")->fetchColumn();
|
||||
if ($permissionId > 0 && $superRoleId > 0) {
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM admin_role_permissions WHERE role_id = ? AND permission_id = ?');
|
||||
$stmt->execute([$superRoleId, $permissionId]);
|
||||
if ((int)$stmt->fetchColumn() === 0) {
|
||||
$insert = $pdo->prepare('INSERT INTO admin_role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)');
|
||||
$insert->execute([$superRoleId, $permissionId, $now]);
|
||||
echo "ADD_SUPER_ADMIN_PERMISSION materials.manage\n";
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SELECT id FROM admin_roles WHERE code = ?');
|
||||
$stmt->execute(['material_manager']);
|
||||
$materialRoleId = (int)$stmt->fetchColumn();
|
||||
if ($materialRoleId <= 0) {
|
||||
$insert = $pdo->prepare('INSERT INTO admin_roles (name, code, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?)');
|
||||
$insert->execute(['物料管理员', 'material_manager', 'enabled', $now, $now]);
|
||||
$materialRoleId = (int)$pdo->lastInsertId();
|
||||
echo "ADD_ROLE material_manager\n";
|
||||
}
|
||||
|
||||
if ($materialRoleId > 0 && $permissionId > 0) {
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM admin_role_permissions WHERE role_id = ? AND permission_id = ?');
|
||||
$stmt->execute([$materialRoleId, $permissionId]);
|
||||
if ((int)$stmt->fetchColumn() === 0) {
|
||||
$insert = $pdo->prepare('INSERT INTO admin_role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)');
|
||||
$insert->execute([$materialRoleId, $permissionId, $now]);
|
||||
echo "ADD_MATERIAL_MANAGER_PERMISSION materials.manage\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "SCHEMA_UPGRADE_OK\n";
|
||||
47
server-api/tools/schema_upgrade_order_return_flow.php
Normal file
47
server-api/tools/schema_upgrade_order_return_flow.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE IF NOT EXISTS order_return_addresses (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
user_address_id BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||
consignee VARCHAR(64) NOT NULL DEFAULT '',
|
||||
mobile VARCHAR(32) NOT NULL DEFAULT '',
|
||||
province VARCHAR(64) NOT NULL DEFAULT '',
|
||||
city VARCHAR(64) NOT NULL DEFAULT '',
|
||||
district VARCHAR(64) NOT NULL DEFAULT '',
|
||||
detail_address VARCHAR(255) NOT NULL DEFAULT '',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_order_return_addresses_order_id (order_id),
|
||||
KEY idx_order_return_addresses_user_address_id (user_address_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单寄回地址快照'
|
||||
SQL);
|
||||
|
||||
echo "SCHEMA_UPGRADE_OK\n";
|
||||
52
server-api/tools/schema_upgrade_order_shipping_targets.php
Normal file
52
server-api/tools/schema_upgrade_order_shipping_targets.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE IF NOT EXISTS order_shipping_targets (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
warehouse_id BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||
warehouse_name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
warehouse_code VARCHAR(64) NOT NULL DEFAULT '',
|
||||
service_provider VARCHAR(32) NOT NULL DEFAULT 'anxinyan',
|
||||
receiver_name VARCHAR(64) NOT NULL DEFAULT '',
|
||||
receiver_mobile VARCHAR(32) NOT NULL DEFAULT '',
|
||||
province VARCHAR(64) NOT NULL DEFAULT '',
|
||||
city VARCHAR(64) NOT NULL DEFAULT '',
|
||||
district VARCHAR(64) NOT NULL DEFAULT '',
|
||||
detail_address VARCHAR(255) NOT NULL DEFAULT '',
|
||||
service_time VARCHAR(128) NOT NULL DEFAULT '',
|
||||
notice VARCHAR(500) NOT NULL DEFAULT '',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_order_shipping_targets_order_id (order_id),
|
||||
KEY idx_order_shipping_targets_warehouse_id (warehouse_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单锁定仓库快照'
|
||||
SQL);
|
||||
|
||||
echo "SCHEMA_UPGRADE_OK\n";
|
||||
61
server-api/tools/schema_upgrade_order_source_channel.php
Normal file
61
server-api/tools/schema_upgrade_order_source_channel.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
function hasColumn(PDO $pdo, string $table, string $column): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?');
|
||||
$stmt->execute([$table, $column]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
function hasIndex(PDO $pdo, string $table, string $index): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ?');
|
||||
$stmt->execute([$table, $index]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
if (!hasColumn($pdo, 'orders', 'source_customer_id')) {
|
||||
$pdo->exec("ALTER TABLE orders ADD COLUMN source_customer_id VARCHAR(64) NOT NULL DEFAULT '' AFTER source_channel");
|
||||
echo "ADD_COLUMN orders.source_customer_id\n";
|
||||
}
|
||||
|
||||
$pdo->exec("ALTER TABLE orders MODIFY COLUMN source_channel VARCHAR(32) NOT NULL DEFAULT 'mini_program'");
|
||||
|
||||
if (!hasIndex($pdo, 'orders', 'idx_orders_source_channel')) {
|
||||
$pdo->exec('ALTER TABLE orders ADD KEY idx_orders_source_channel (source_channel)');
|
||||
echo "ADD_INDEX orders.idx_orders_source_channel\n";
|
||||
}
|
||||
|
||||
if (!hasIndex($pdo, 'orders', 'idx_orders_source_customer_id')) {
|
||||
$pdo->exec('ALTER TABLE orders ADD KEY idx_orders_source_customer_id (source_customer_id)');
|
||||
echo "ADD_INDEX orders.idx_orders_source_customer_id\n";
|
||||
}
|
||||
|
||||
$pdo->exec("UPDATE orders SET source_channel = 'mini_program' WHERE source_channel = 'user_app'");
|
||||
|
||||
echo "SCHEMA_UPGRADE_OK\n";
|
||||
123
server-api/tools/schema_upgrade_user_login_sms.php
Normal file
123
server-api/tools/schema_upgrade_user_login_sms.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
function hasColumn(PDO $pdo, string $table, string $column): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?');
|
||||
$stmt->execute([$table, $column]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
function hasTable(PDO $pdo, string $table): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?');
|
||||
$stmt->execute([$table]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
function hasSystemConfig(PDO $pdo, string $group, string $key): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM system_configs WHERE config_group = ? AND config_key = ?');
|
||||
$stmt->execute([$group, $key]);
|
||||
return (int)$stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
if (!hasColumn($pdo, 'users', 'password')) {
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN password VARCHAR(255) NOT NULL DEFAULT '' AFTER mobile");
|
||||
echo "ADD_COLUMN users.password\n";
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'user_api_tokens')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE user_api_tokens (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
token_hash VARCHAR(64) NOT NULL,
|
||||
auth_type VARCHAR(32) NOT NULL DEFAULT 'password',
|
||||
expire_time DATETIME NOT NULL,
|
||||
last_active_at DATETIME NULL DEFAULT NULL,
|
||||
last_ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
user_agent VARCHAR(500) NOT NULL DEFAULT '',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_user_api_tokens_token_hash (token_hash),
|
||||
KEY idx_user_api_tokens_user_id (user_id),
|
||||
KEY idx_user_api_tokens_expire_time (expire_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户登录Token'
|
||||
SQL);
|
||||
echo "CREATE_TABLE user_api_tokens\n";
|
||||
}
|
||||
|
||||
if (!hasTable($pdo, 'sms_code_logs')) {
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE sms_code_logs (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
mobile VARCHAR(32) NOT NULL,
|
||||
scene VARCHAR(32) NOT NULL DEFAULT 'login',
|
||||
code_hash VARCHAR(64) NOT NULL,
|
||||
send_status VARCHAR(32) NOT NULL DEFAULT 'success',
|
||||
provider VARCHAR(32) NOT NULL DEFAULT 'aliyun_sms',
|
||||
template_code VARCHAR(64) NOT NULL DEFAULT '',
|
||||
request_id VARCHAR(128) NOT NULL DEFAULT '',
|
||||
biz_id VARCHAR(128) NOT NULL DEFAULT '',
|
||||
failed_reason VARCHAR(255) NOT NULL DEFAULT '',
|
||||
expire_time DATETIME NOT NULL,
|
||||
used_at DATETIME NULL DEFAULT NULL,
|
||||
send_ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_sms_code_logs_mobile_scene (mobile, scene),
|
||||
KEY idx_sms_code_logs_expire_time (expire_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='短信验证码发送记录'
|
||||
SQL);
|
||||
echo "CREATE_TABLE sms_code_logs\n";
|
||||
}
|
||||
|
||||
$configs = [
|
||||
['sms', 'access_key_id', ''],
|
||||
['sms', 'access_key_secret', ''],
|
||||
['sms', 'sign_name', ''],
|
||||
['sms', 'login_template_code', ''],
|
||||
['sms', 'region_id', 'cn-hangzhou'],
|
||||
['sms', 'endpoint', ''],
|
||||
];
|
||||
|
||||
foreach ($configs as [$group, $key, $value]) {
|
||||
if (hasSystemConfig($pdo, $group, $key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('INSERT INTO system_configs (config_group, config_key, config_value, remark, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)');
|
||||
$stmt->execute([$group, $key, $value, '后台系统配置', $now, $now]);
|
||||
echo "ADD_CONFIG {$group}.{$key}\n";
|
||||
}
|
||||
|
||||
echo "SCHEMA_UPGRADE_OK\n";
|
||||
80
server-api/tools/schema_upgrade_warehouses.php
Normal file
80
server-api/tools/schema_upgrade_warehouses.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
$pdo->exec(<<<'SQL'
|
||||
CREATE TABLE IF NOT EXISTS shipping_warehouses (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
warehouse_name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
warehouse_code VARCHAR(64) NOT NULL DEFAULT '',
|
||||
warehouse_type VARCHAR(32) NOT NULL DEFAULT 'detection_center',
|
||||
service_provider VARCHAR(32) NOT NULL DEFAULT 'anxinyan',
|
||||
receiver_name VARCHAR(64) NOT NULL DEFAULT '',
|
||||
receiver_mobile VARCHAR(32) NOT NULL DEFAULT '',
|
||||
province VARCHAR(64) NOT NULL DEFAULT '',
|
||||
city VARCHAR(64) NOT NULL DEFAULT '',
|
||||
district VARCHAR(64) NOT NULL DEFAULT '',
|
||||
detail_address VARCHAR(255) NOT NULL DEFAULT '',
|
||||
service_time VARCHAR(128) NOT NULL DEFAULT '',
|
||||
notice VARCHAR(500) NOT NULL DEFAULT '',
|
||||
supported_category_ids_json JSON NULL,
|
||||
service_area_provinces_json JSON NULL,
|
||||
service_area_cities_json JSON NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'enabled',
|
||||
is_default TINYINT(1) NOT NULL DEFAULT 0,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_shipping_warehouses_code (warehouse_code),
|
||||
KEY idx_shipping_warehouses_service_provider (service_provider),
|
||||
KEY idx_shipping_warehouses_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='收货仓库 / 检测中心'
|
||||
SQL);
|
||||
|
||||
$provinceColumn = $pdo->query("SHOW COLUMNS FROM shipping_warehouses LIKE 'service_area_provinces_json'")->fetch();
|
||||
if (!$provinceColumn) {
|
||||
$pdo->exec("ALTER TABLE shipping_warehouses ADD COLUMN service_area_provinces_json JSON NULL AFTER supported_category_ids_json");
|
||||
echo "ADD_COLUMN shipping_warehouses.service_area_provinces_json\n";
|
||||
}
|
||||
|
||||
$cityColumn = $pdo->query("SHOW COLUMNS FROM shipping_warehouses LIKE 'service_area_cities_json'")->fetch();
|
||||
if (!$cityColumn) {
|
||||
$pdo->exec("ALTER TABLE shipping_warehouses ADD COLUMN service_area_cities_json JSON NULL AFTER service_area_provinces_json");
|
||||
echo "ADD_COLUMN shipping_warehouses.service_area_cities_json\n";
|
||||
}
|
||||
|
||||
$count = (int)$pdo->query('SELECT COUNT(*) FROM shipping_warehouses')->fetchColumn();
|
||||
if ($count === 0) {
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$stmt = $pdo->prepare('INSERT INTO shipping_warehouses (warehouse_name, warehouse_code, warehouse_type, service_provider, receiver_name, receiver_mobile, province, city, district, detail_address, service_time, notice, supported_category_ids_json, status, is_default, sort_order, remark, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?)');
|
||||
$stmt->execute(['安心验鉴定中心', 'AXY-WH-DEFAULT', 'detection_center', 'anxinyan', '安心验鉴定中心', '400-800-1314', '广东省', '深圳市', '南山区', '科技园鉴定路 88 号 安心验收件中心', '周一至周日 09:30-18:30', '寄送前请确认订单信息完整,包裹内附上订单号可提升签收后的处理效率。', 'enabled', 1, 1, '默认仓库', $now, $now]);
|
||||
$stmt->execute(['中检合作鉴定中心', 'ZJ-WH-DEFAULT', 'detection_center', 'zhongjian', '中检合作鉴定中心', '400-800-1314', '广东省', '深圳市', '南山区', '科技园鉴定路 88 号 安心验中检收件中心', '周一至周日 09:30-18:30', '中检鉴定订单请优先附上鉴定单号,寄出后尽快填写运单号。', 'enabled', 1, 1, '默认仓库', $now, $now]);
|
||||
echo "SEED_DEFAULT_WAREHOUSES\n";
|
||||
}
|
||||
|
||||
echo "SCHEMA_UPGRADE_OK\n";
|
||||
151
server-api/tools/smoke_check.php
Normal file
151
server-api/tools/smoke_check.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$baseUrl = 'http://127.0.0.1:8787';
|
||||
|
||||
function requestJson(string $method, string $url, array $payload = [], array $headers = []): array
|
||||
{
|
||||
$ch = curl_init();
|
||||
$defaultHeaders = ['Accept: application/json'];
|
||||
if ($payload) {
|
||||
$defaultHeaders[] = 'Content-Type: application/json';
|
||||
}
|
||||
foreach ($headers as $header) {
|
||||
$defaultHeaders[] = $header;
|
||||
}
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_HTTPHEADER => $defaultHeaders,
|
||||
]);
|
||||
|
||||
if ($payload) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
$error = curl_error($ch);
|
||||
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($errno) {
|
||||
throw new RuntimeException("HTTP {$method} {$url} failed: {$error}");
|
||||
}
|
||||
|
||||
$decoded = json_decode((string)$response, true);
|
||||
return [
|
||||
'status' => $httpCode,
|
||||
'body' => is_array($decoded) ? $decoded : ['raw' => $response],
|
||||
];
|
||||
}
|
||||
|
||||
function assertOk(string $label, array $response): void
|
||||
{
|
||||
$status = $response['status'];
|
||||
$body = $response['body'];
|
||||
$code = $body['code'] ?? null;
|
||||
if ($status !== 200 || $code !== 0) {
|
||||
throw new RuntimeException("{$label} failed: http={$status} body=" . json_encode($body, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
echo "[PASS] {$label}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
assertOk('app home', requestJson('GET', $baseUrl . '/api/app/home/index'));
|
||||
assertOk('app help center', requestJson('GET', $baseUrl . '/api/app/help-center'));
|
||||
|
||||
$appLogin = requestJson('POST', $baseUrl . '/api/app/auth/login/password', [
|
||||
'mobile' => '13800000000',
|
||||
'password' => 'User@123456',
|
||||
]);
|
||||
assertOk('app login', $appLogin);
|
||||
$appToken = $appLogin['body']['data']['token'] ?? '';
|
||||
if (!$appToken) {
|
||||
throw new RuntimeException('app login did not return token');
|
||||
}
|
||||
$appAuthHeader = ['Authorization: Bearer ' . $appToken];
|
||||
|
||||
assertOk('app me', requestJson('GET', $baseUrl . '/api/app/auth/me', [], $appAuthHeader));
|
||||
$appReports = requestJson('GET', $baseUrl . '/api/app/reports', [], $appAuthHeader);
|
||||
assertOk('app reports', $appReports);
|
||||
$appOrders = requestJson('GET', $baseUrl . '/api/app/orders', [], $appAuthHeader);
|
||||
assertOk('app orders', $appOrders);
|
||||
assertOk('app messages summary', requestJson('GET', $baseUrl . '/api/app/messages/summary', [], $appAuthHeader));
|
||||
assertOk('app addresses', requestJson('GET', $baseUrl . '/api/app/addresses', [], $appAuthHeader));
|
||||
assertOk('app settings', requestJson('GET', $baseUrl . '/api/app/settings', [], $appAuthHeader));
|
||||
|
||||
$completedOrder = null;
|
||||
foreach (($appOrders['body']['data']['list'] ?? []) as $item) {
|
||||
if (($item['order_status'] ?? '') === 'completed') {
|
||||
$completedOrder = $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($completedOrder) {
|
||||
$orderDetail = requestJson('GET', $baseUrl . '/api/app/order/detail?id=' . (int)$completedOrder['order_id'], [], $appAuthHeader);
|
||||
assertOk('app order detail completed', $orderDetail);
|
||||
}
|
||||
|
||||
$reportNo = $appReports['body']['data']['list'][0]['report_no'] ?? '';
|
||||
if ($reportNo !== '') {
|
||||
$reportDetail = requestJson('GET', $baseUrl . '/api/app/report/detail?report_no=' . rawurlencode($reportNo));
|
||||
assertOk('app public report detail', $reportDetail);
|
||||
$verifyQr = $reportDetail['body']['data']['verify_info']['verify_qrcode_url'] ?? '';
|
||||
if ($verifyQr === '') {
|
||||
throw new RuntimeException('app public report detail missing verify_qrcode_url');
|
||||
}
|
||||
assertOk('app public verify', requestJson('GET', $baseUrl . '/api/app/verify?report_no=' . rawurlencode($reportNo)));
|
||||
}
|
||||
|
||||
$appLogout = requestJson('POST', $baseUrl . '/api/app/auth/logout', [], $appAuthHeader);
|
||||
assertOk('app logout', $appLogout);
|
||||
|
||||
$login = requestJson('POST', $baseUrl . '/api/admin/auth/login', [
|
||||
'mobile' => '13800138000',
|
||||
'password' => 'Anxinyan@2026!',
|
||||
]);
|
||||
assertOk('admin login', $login);
|
||||
$token = $login['body']['data']['token'] ?? '';
|
||||
if (!$token) {
|
||||
throw new RuntimeException('admin login did not return token');
|
||||
}
|
||||
$authHeader = ['Authorization: Bearer ' . $token];
|
||||
|
||||
assertOk('admin me', requestJson('GET', $baseUrl . '/api/admin/auth/me', [], $authHeader));
|
||||
assertOk('admin dashboard', requestJson('GET', $baseUrl . '/api/admin/dashboard', [], $authHeader));
|
||||
assertOk('admin users overview', requestJson('GET', $baseUrl . '/api/admin/users/overview', [], $authHeader));
|
||||
assertOk('admin access overview', requestJson('GET', $baseUrl . '/api/admin/access/overview', [], $authHeader));
|
||||
assertOk('admin system configs', requestJson('GET', $baseUrl . '/api/admin/system-configs', [], $authHeader));
|
||||
|
||||
$adminOrders = requestJson('GET', $baseUrl . '/api/admin/orders', [], $authHeader);
|
||||
assertOk('admin orders', $adminOrders);
|
||||
$adminCompletedOrder = null;
|
||||
foreach (($adminOrders['body']['data']['list'] ?? []) as $item) {
|
||||
if (($item['order_status'] ?? '') === 'completed') {
|
||||
$adminCompletedOrder = $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($adminCompletedOrder) {
|
||||
$adminOrderDetail = requestJson('GET', $baseUrl . '/api/admin/order/detail?id=' . (int)$adminCompletedOrder['id'], [], $authHeader);
|
||||
assertOk('admin order detail completed', $adminOrderDetail);
|
||||
}
|
||||
|
||||
$logout = requestJson('POST', $baseUrl . '/api/admin/auth/logout', [], $authHeader);
|
||||
assertOk('admin logout', $logout);
|
||||
|
||||
echo "SMOKE_OK\n";
|
||||
} catch (Throwable $e) {
|
||||
fwrite(STDERR, "SMOKE_FAIL: " . $e->getMessage() . "\n");
|
||||
exit(1);
|
||||
}
|
||||
73
server-api/tools/sync_client_configs.php
Normal file
73
server-api/tools/sync_client_configs.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$projectRoot = dirname(__DIR__, 2);
|
||||
$manifestPath = $projectRoot . '/user-app/src/manifest.json';
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
$configRows = $pdo->query("SELECT config_group, config_key, config_value FROM system_configs")->fetchAll();
|
||||
$configMap = [];
|
||||
foreach ($configRows as $row) {
|
||||
$configMap[$row['config_group'] . '.' . $row['config_key']] = (string)($row['config_value'] ?? '');
|
||||
}
|
||||
|
||||
$miniProgramAppId = trim($configMap['mini_program.app_id'] ?? '');
|
||||
if ($miniProgramAppId === '') {
|
||||
throw new RuntimeException('后台系统配置 mini_program.app_id 为空,无法同步到 user-app manifest。');
|
||||
}
|
||||
|
||||
$manifestContent = @file_get_contents($manifestPath);
|
||||
if ($manifestContent === false) {
|
||||
throw new RuntimeException('无法读取 user-app/src/manifest.json。');
|
||||
}
|
||||
|
||||
$pattern = '/("mp-weixin"\s*:\s*\{.*?"appid"\s*:\s*")([^"]*)(")/s';
|
||||
if (!preg_match($pattern, $manifestContent)) {
|
||||
throw new RuntimeException('未在 manifest.json 中找到 mp-weixin.appid 字段。');
|
||||
}
|
||||
|
||||
$updatedContent = preg_replace_callback(
|
||||
$pattern,
|
||||
static fn(array $matches): string => $matches[1] . $miniProgramAppId . $matches[3],
|
||||
$manifestContent,
|
||||
1
|
||||
);
|
||||
|
||||
if (!is_string($updatedContent) || $updatedContent === '') {
|
||||
throw new RuntimeException('同步 manifest.json 失败。');
|
||||
}
|
||||
|
||||
if (@file_put_contents($manifestPath, $updatedContent) === false) {
|
||||
throw new RuntimeException('写入 user-app/src/manifest.json 失败。');
|
||||
}
|
||||
|
||||
echo "SYNC_CLIENT_CONFIG_OK\n";
|
||||
echo "mini_program.app_id => {$miniProgramAppId}\n";
|
||||
} catch (Throwable $e) {
|
||||
fwrite(STDERR, "SYNC_CLIENT_CONFIG_FAIL: {$e->getMessage()}\n");
|
||||
exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user