This commit is contained in:
wushumin
2026-05-11 15:28:27 +08:00
commit 9aac78b8da
289 changed files with 67193 additions and 0 deletions

View 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;
}

View 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";

View 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;
}

View 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";

View 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);

View 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";
}

View 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";

View 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";

View 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";

View 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";

View 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";

View 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";

View 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";

View 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";

View 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";

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

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