feat: add multi-company Feishu integration and data migration
- 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>
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
<?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);
|
||||
}
|
||||
Reference in New Issue
Block a user