1f6d346f6a
- Add companies table enhancements (abbr, tenant_key, feishu_enabled) - Add users table enhancements (feishu fields, sn, report_to, etc.) - Create company_feishu_apps table for multi-app per company - Create user_sn_pool table for user number management - Update FeishuService to support multiple Feishu apps per company - Add data migration script (002_merge_old_data.sql) - Add import script (scripts/import_old_data.php) - Update User and Company models with new scopes and relationships - Add CompanyFeishuApp model - Add Git workflow documentation (GIT_WORKFLOW.md) This supports: - Multi-company architecture with Feishu integration - Multiple Feishu apps per company (X-Server, Ticket, CRM) - User number assignment system - Data migration from old project Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
271 lines
8.6 KiB
PHP
271 lines
8.6 KiB
PHP
<?php
|
|
|
|
/**
|
|
* 数据迁移脚本
|
|
* 从旧项目导入用户、公司和飞书应用数据
|
|
*
|
|
* 使用方法:
|
|
* php scripts/import_old_data.php /path/to/fdesk.sql
|
|
*/
|
|
|
|
require_once __DIR__ . '/../vendor/autoload.php';
|
|
require_once __DIR__ . '/../app/Util/Db.php';
|
|
|
|
use App\Util\Db;
|
|
|
|
class DataImporter
|
|
{
|
|
private $pdo;
|
|
private $sourcePdo = null;
|
|
|
|
public function __construct()
|
|
{
|
|
// 初始化数据库连接
|
|
Db::init();
|
|
$this->pdo = Db::connection()->getPdo();
|
|
}
|
|
|
|
/**
|
|
* 从SQL文件导入数据
|
|
*/
|
|
public function importFromSqlFile(string $sqlFile): void
|
|
{
|
|
echo "开始从 SQL 文件导入数据...\n";
|
|
|
|
// 创建临时数据库用于导入旧数据
|
|
$tempDbName = 'temp_fdesk_import';
|
|
|
|
try {
|
|
// 连接到MySQL服务器(不指定数据库)
|
|
$dsn = sprintf(
|
|
'mysql:host=%s;port=%s;charset=utf8mb4',
|
|
getenv('DB_HOST') ?: 'mysql',
|
|
getenv('DB_PORT') ?: '3306'
|
|
);
|
|
$rootPdo = new PDO($dsn, 'root', getenv('DB_PASSWORD') ?: '123456');
|
|
$rootPdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
// 删除临时数据库(如果存在)
|
|
$rootPdo->exec("DROP DATABASE IF EXISTS `{$tempDbName}`");
|
|
|
|
// 创建临时数据库
|
|
$rootPdo->exec("CREATE DATABASE `{$tempDbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
|
echo "✓ 临时数据库创建成功\n";
|
|
|
|
// 导入SQL文件到临时数据库
|
|
$sql = file_get_contents($sqlFile);
|
|
if ($sql === false) {
|
|
throw new Exception("无法读取 SQL 文件");
|
|
}
|
|
|
|
// 移除SQL文件中的创建数据库语句
|
|
$sql = preg_replace('/CREATE DATABASE.*?;/is', '', $sql);
|
|
$sql = preg_replace('/USE .*?;/is', "USE `{$tempDbName}`;", $sql);
|
|
|
|
$rootPdo->exec($sql);
|
|
echo "✓ SQL 文件导入临时数据库成功\n";
|
|
|
|
// 连接到临时数据库
|
|
$this->sourcePdo = new PDO(
|
|
$dsn . ';dbname=' . $tempDbName,
|
|
'root',
|
|
getenv('DB_PASSWORD') ?: '123456'
|
|
);
|
|
$this->sourcePdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
// 开始迁移数据
|
|
$this->migrateData();
|
|
|
|
// 清理临时数据库
|
|
$rootPdo->exec("DROP DATABASE IF EXISTS `{$tempDbName}`");
|
|
echo "✓ 临时数据库已清理\n";
|
|
|
|
echo "\n数据迁移完成!\n";
|
|
|
|
} catch (Exception $e) {
|
|
echo "✗ 导入失败: " . $e->getMessage() . "\n";
|
|
|
|
// 清理临时数据库
|
|
if (isset($rootPdo)) {
|
|
$rootPdo->exec("DROP DATABASE IF EXISTS `{$tempDbName}`");
|
|
}
|
|
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 迁移数据
|
|
*/
|
|
private function migrateData(): void
|
|
{
|
|
$this->migrateCompanies();
|
|
$this->migrateFeishuApps();
|
|
$this->migrateUsers();
|
|
}
|
|
|
|
/**
|
|
* 迁移公司数据
|
|
*/
|
|
private function migrateCompanies(): void
|
|
{
|
|
echo "\n迁移公司数据...\n";
|
|
|
|
$stmt = $this->sourcePdo->query("SELECT * FROM corp");
|
|
$companies = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($companies as $company) {
|
|
$sql = "INSERT INTO companies
|
|
(id, name, abbr, tenant_key, sort, status, created_at, updated_at, feishu_enabled)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)
|
|
ON DUPLICATE KEY UPDATE
|
|
name = VALUES(name),
|
|
abbr = VALUES(abbr),
|
|
tenant_key = VALUES(tenant_key)
|
|
";
|
|
|
|
try {
|
|
$this->pdo->prepare($sql)->execute([
|
|
$company['id'],
|
|
$company['name'],
|
|
$company['abbr'],
|
|
$company['tenant_key'],
|
|
$company['sort'],
|
|
$company['status'],
|
|
$company['created_at'],
|
|
$company['updated_at'],
|
|
]);
|
|
echo " ✓ {$company['name']} (ID: {$company['id']})\n";
|
|
} catch (Exception $e) {
|
|
echo " ✗ {$company['name']} 导入失败: " . $e->getMessage() . "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 迁移飞书应用配置
|
|
*/
|
|
private function migrateFeishuApps(): void
|
|
{
|
|
echo "\n迁移飞书应用配置...\n";
|
|
|
|
$stmt = $this->sourcePdo->query("SELECT * FROM corp_feishu_app");
|
|
$apps = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($apps as $app) {
|
|
$sql = "INSERT INTO company_feishu_apps
|
|
(id, company_id, system_id, name, tenant_key, app_id, app_secret,
|
|
base_uri, callback_url, mobile_callback_url, scopes, state, sort, status, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
app_id = VALUES(app_id),
|
|
app_secret = VALUES(app_secret)
|
|
";
|
|
|
|
try {
|
|
$this->pdo->prepare($sql)->execute([
|
|
$app['id'],
|
|
$app['corp_id'],
|
|
$app['system_id'],
|
|
$app['name'],
|
|
$app['tenant_key'],
|
|
$app['app_id'],
|
|
$app['app_secret'],
|
|
$app['base_uri'],
|
|
$app['r_url'],
|
|
$app['m_url'],
|
|
$app['scopes'],
|
|
$app['state'],
|
|
$app['sort'],
|
|
$app['status'],
|
|
$app['created_at'],
|
|
$app['updated_at'],
|
|
]);
|
|
echo " ✓ {$app['name']} - 公司ID: {$app['corp_id']}\n";
|
|
} catch (Exception $e) {
|
|
echo " ✗ {$app['name']} 导入失败: " . $e->getMessage() . "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 迁移用户数据
|
|
*/
|
|
private function migrateUsers(): void
|
|
{
|
|
echo "\n迁移用户数据...\n";
|
|
|
|
$stmt = $this->sourcePdo->query("SELECT * FROM `user` LIMIT 1000");
|
|
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($users as $user) {
|
|
// 检查用户是否已存在
|
|
$checkSql = "SELECT id FROM users WHERE id = ?";
|
|
$checkStmt = $this->pdo->prepare($checkSql);
|
|
$checkStmt->execute([$user['id']]);
|
|
|
|
if ($checkStmt->fetch()) {
|
|
echo " ⊘ {$user['name']} (ID: {$user['id']}) - 已存在,跳过\n";
|
|
continue;
|
|
}
|
|
|
|
$sql = "INSERT INTO users
|
|
(id, sn, report_to, name, avatar, sex, password, phone, email, company_id,
|
|
status, user_type, union_id, feishu_user_id, feishu_open_id, tenant_key,
|
|
position, sign_img, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
";
|
|
|
|
try {
|
|
$this->pdo->prepare($sql)->execute([
|
|
$user['id'],
|
|
$user['sn'],
|
|
$user['report_to'],
|
|
$user['name'],
|
|
$user['avatar'],
|
|
$user['sex'],
|
|
$user['password'],
|
|
$user['phone'],
|
|
$user['email'],
|
|
$user['corp_id'],
|
|
$user['status'],
|
|
$user['user_type'],
|
|
$user['union_id'],
|
|
$user['user_id'],
|
|
$user['open_id'],
|
|
$user['tenant_key'],
|
|
$user['position'],
|
|
$user['sign_img'],
|
|
$user['created_at'],
|
|
$user['updated_at'],
|
|
]);
|
|
echo " ✓ {$user['name']} (ID: {$user['id']}, {$user['email']})\n";
|
|
} catch (Exception $e) {
|
|
echo " ✗ {$user['name']} 导入失败: " . $e->getMessage() . "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 主执行
|
|
if ($argc < 2) {
|
|
echo "用法: php " . basename(__FILE__) . " <fdesk.sql文件路径>\n";
|
|
echo "示例: php " . basename(__FILE__) . " /www/x-server-mobile/fdesk.sql\n";
|
|
exit(1);
|
|
}
|
|
|
|
$sqlFile = $argv[1];
|
|
if (!file_exists($sqlFile)) {
|
|
echo "错误: SQL 文件不存在: {$sqlFile}\n";
|
|
exit(1);
|
|
}
|
|
|
|
try {
|
|
$importer = new DataImporter();
|
|
$importer->importFromSqlFile($sqlFile);
|
|
} catch (Exception $e) {
|
|
echo "\n错误: " . $e->getMessage() . "\n";
|
|
echo "堆栈: " . $e->getTraceAsString() . "\n";
|
|
exit(1);
|
|
}
|