Files
coolcoth.com/install/install.php
T
2026-08-08 15:53:53 +08:00

183 lines
9.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 安装 / 初始化脚本
* 用法:
* CLI: php install/install.php
* Web: 浏览器访问 /install/install.php
* 根据 config 中 app.driver 写入 文件(JSON) 或 MySQL,并生成 theme.css。
*/
define('BASE_PATH', dirname(__DIR__));
require BASE_PATH . '/app/Core/App.php';
Core\App::init();
$cli = PHP_SAPI === 'cli';
$out = function (string $msg) use ($cli) {
echo $cli ? $msg . "\n" : "<p>" . htmlspecialchars($msg) . "</p>\n";
};
/**
* 增量升级模式:仅给已部署的系统补齐新模块的数据表/列,绝不触碰已有业务数据。
* 与 install/upgrade_crm_psi.sql 逻辑等价,但改用 PDO 直连(避免 DELIMITER/存储过程在 PHP 侧不兼容)。
* 用法:php install/install.php --upgrade 或 Web: /install/install.php?upgrade=1
*/
function runUpgrade(): void
{
global $out;
$driver = Core\Db::driver();
if ($driver !== 'mysql') {
$out("⚠ 当前为 file 模式,CRM/PSI 在文件模式下自动生成 JSON,无需数据库升级。");
return;
}
$pdo = Core\Db::pdo();
$pdo->exec("SET NAMES utf8mb4");
$stmts = [
// ===== CRM 客户管理 =====
"CREATE TABLE IF NOT EXISTS `crm_customers` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(120) NOT NULL, `company` VARCHAR(160) DEFAULT '',
`contact` VARCHAR(60) DEFAULT '', `phone` VARCHAR(40) DEFAULT '', `email` VARCHAR(120) DEFAULT '',
`country` VARCHAR(60) DEFAULT '', `type` VARCHAR(20) DEFAULT 'trade', `source` VARCHAR(40) DEFAULT '',
`level` VARCHAR(20) DEFAULT 'C', `remark` TEXT, `owner` VARCHAR(60) DEFAULT '', `created_at` VARCHAR(20) DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS `crm_leads` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `customer_id` INT DEFAULT 0, `title` VARCHAR(200) NOT NULL,
`amount` DECIMAL(12,2) DEFAULT 0, `stage` VARCHAR(20) DEFAULT 'new', `expected_close` VARCHAR(20) DEFAULT '',
`owner` VARCHAR(60) DEFAULT '', `remark` TEXT, `created_at` VARCHAR(20) DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS `crm_followups` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `customer_id` INT DEFAULT 0, `lead_id` INT DEFAULT 0,
`content` TEXT, `next_at` VARCHAR(20) DEFAULT '', `owner` VARCHAR(60) DEFAULT '', `created_at` VARCHAR(20) DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
// ===== 进销存 PSI =====
"CREATE TABLE IF NOT EXISTS `psi_materials` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `code` VARCHAR(40) DEFAULT '', `name` VARCHAR(160) NOT NULL,
`spec` VARCHAR(120) DEFAULT '', `unit` VARCHAR(10) DEFAULT '个', `category` VARCHAR(40) DEFAULT '',
`stock` DECIMAL(12,2) DEFAULT 0, `price` DECIMAL(10,2) DEFAULT 0, `supplier_id` INT DEFAULT 0,
`remark` TEXT, `created_at` VARCHAR(20) DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS `psi_products` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `code` VARCHAR(40) DEFAULT '', `name` VARCHAR(160) NOT NULL,
`spec` VARCHAR(120) DEFAULT '', `unit` VARCHAR(10) DEFAULT '件', `category` VARCHAR(40) DEFAULT '',
`stock` DECIMAL(12,2) DEFAULT 0, `cost` DECIMAL(10,2) DEFAULT 0, `price` DECIMAL(10,2) DEFAULT 0,
`remark` TEXT, `created_at` VARCHAR(20) DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS `psi_suppliers` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(160) NOT NULL, `contact` VARCHAR(60) DEFAULT '',
`phone` VARCHAR(40) DEFAULT '', `country` VARCHAR(60) DEFAULT '', `remark` TEXT, `created_at` VARCHAR(20) DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS `psi_purchases` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `order_no` VARCHAR(40) NOT NULL, `supplier_id` INT DEFAULT 0,
`item_type` VARCHAR(10) DEFAULT 'material', `item_id` INT DEFAULT 0, `qty` DECIMAL(12,2) DEFAULT 0,
`price` DECIMAL(10,2) DEFAULT 0, `amount` DECIMAL(12,2) DEFAULT 0, `status` VARCHAR(20) DEFAULT 'pending',
`remark` TEXT, `created_at` VARCHAR(20) DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS `psi_sales` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `order_no` VARCHAR(40) NOT NULL, `customer` VARCHAR(160) DEFAULT '',
`channel` VARCHAR(20) DEFAULT 'domestic', `item_id` INT DEFAULT 0, `qty` DECIMAL(12,2) DEFAULT 0,
`price` DECIMAL(10,2) DEFAULT 0, `amount` DECIMAL(12,2) DEFAULT 0, `status` VARCHAR(20) DEFAULT 'pending',
`remark` TEXT, `created_at` VARCHAR(20) DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS `psi_stock_moves` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `item_type` VARCHAR(10) DEFAULT 'product', `item_id` INT DEFAULT 0,
`direction` VARCHAR(10) DEFAULT 'in', `qty` DECIMAL(12,2) DEFAULT 0, `ref_no` VARCHAR(40) DEFAULT '',
`remark` TEXT, `created_at` VARCHAR(20) DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
];
foreach ($stmts as $s) { $pdo->exec($s); }
$out("✓ CRM/PSI 数据表已就绪(CREATE TABLE IF NOT EXISTS,幂等可重复执行)");
// 为 admin_users 补齐分系统角色列(先查 information_schema,存在则跳过,兼容 5.7/8.0
$cols = $pdo->query("SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='admin_users'")->fetchAll(PDO::FETCH_COLUMN);
foreach (['crm_role' => "VARCHAR(20) DEFAULT 'none'", 'psi_role' => "VARCHAR(20) DEFAULT 'none'"] as $c => $def) {
if (!in_array($c, $cols, true)) {
$pdo->exec("ALTER TABLE `admin_users` ADD COLUMN `{$c}` {$def}");
$out("✓ 已添加 admin_users.{$c}");
} else {
$out("⊘ admin_users.{$c} 已存在,跳过");
}
}
$out("✓ 数据升级完成。请登录后台 → 用户管理,将相关账号 crm_role / psi_role 设为 admin。");
}
// 升级模式:绕过 installed.lock,仅增量建表/补列
$isUpgrade = ($cli && in_array('--upgrade', $argv ?? [], true))
|| (!$cli && trim((string)($_GET['upgrade'] ?? '')) === '1');
if ($isUpgrade) {
runUpgrade();
exit;
}
// 质量红线:生产环境二次运行保护。已安装后写入 installed.lock,再次访问直接拒绝,避免数据被清空。
$lock = BASE_PATH . '/storage/installed.lock';
if (is_file($lock) && !($cli && in_array('--force', $argv ?? [], true))) {
$out("⛔ 检测到 installed.lock,系统已安装。如需重置请删除该文件或执行:php install/install.php --force");
exit;
}
$seed = require BASE_PATH . '/install/seed.php';
$driver = Core\Db::driver();
if ($driver === 'file') {
$dir = Core\Db::fileDir();
foreach ($seed as $table => $rows) {
// 确保每条记录带 id(如 settings 种子未含 id),否则文件模式无法按主键更新
$i = 1;
foreach ($rows as &$r) { if (!isset($r['id'])) { $r['id'] = $i; } $i++; }
unset($r);
file_put_contents($dir . "/{$table}.json", json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
$out("✓ 写入 {$table}.json (" . count($rows) . " 条)");
}
} else {
// MySQL:建表 + 插入
$sql = file_get_contents(BASE_PATH . '/install/schema.sql');
$pdo = Core\Db::pdo();
$pdo->exec($sql);
$out("✓ 数据表已创建");
// 幂等升级:为已存在的 admin_users 补齐分系统角色列(已有则忽略)
foreach (['crm_role VARCHAR(20) DEFAULT \'none\'', 'psi_role VARCHAR(20) DEFAULT \'none\''] as $col) {
try { $pdo->exec("ALTER TABLE `admin_users` ADD COLUMN IF NOT EXISTS `{$col}`"); }
catch (\Throwable $e) { /* 5.7 不支持 IF NOT EXISTS 时忽略已存在错误 */ }
}
$out("✓ admin_users 分系统角色列已补齐");
$map = [
'categories' => new App\Models\Category(),
'products' => new App\Models\Product(),
'news' => new App\Models\News(),
'pages' => new App\Models\Page(),
'banners' => new App\Models\Banner(),
'cases' => new App\Models\CustomerCase(),
'admin_users'=> new App\Models\AdminUser(),
'settings' => new App\Models\Setting(),
'crm_customers' => new App\Models\CRM\Customer(),
'crm_leads' => new App\Models\CRM\Lead(),
'crm_followups' => new App\Models\CRM\FollowUp(),
'psi_suppliers' => new App\Models\PSI\Supplier(),
'psi_materials' => new App\Models\PSI\Material(),
'psi_products' => new App\Models\PSI\Product(),
'psi_purchases' => new App\Models\PSI\Purchase(),
'psi_sales' => new App\Models\PSI\Sales(),
'psi_stock_moves'=> new App\Models\PSI\StockMove(),
'orders' => new App\Models\Order(),
'payments' => new App\Models\Payment(),
];
foreach ($seed as $table => $rows) {
if (!isset($map[$table])) { $out("⊘ 跳过未映射表 {$table}(无对应 Model"); continue; }
$m = $map[$table];
foreach ($rows as $r) { $m->insert($r); }
$out("✓ 插入 {$table} (" . count($rows) . " 条)");
}
}
// 生成主题 CSS
Core\Theme::regenerate();
$out("✓ 主题样式 theme.css 已生成");
// 写安装锁(Web 模式自动写;CLI 不写,便于二次部署时手动控制)
if (!$cli) {
@file_put_contents($lock, date('Y-m-d H:i:s') . " installed by " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown') . "\n");
$out("✓ 已写入 installed.lock(再次运行 install 将被拒绝)");
}
$out("");
$out($cli ? "安装完成 ✓" : "安装完成 ✓ <p><a href='" . site_url() . "'>访问前台</a> | <a href='" . site_url('admin') . "'>进入后台</a></p>");