/** * V3: 为 ai_providers 表添加 visibility 列(public/private) * * 背景: * - 管理员可发布 public provider,全员可用 * - 普通用户(teacher/student 等)可创建 private provider,仅本人可见 * * 缺失列清单: * - ai_providers.visibility (可见性:public | private,默认 private) * - 索引 ai_provider_visibility_idx (visibility) * - 索引 ai_provider_created_by_idx (created_by) * * 注意:默认值设为 'private',保证历史记录不会意外对全员公开。 * 管理员可在 UI 中将需要共享的 provider 改为 public。 */ require("dotenv/config"); const mysql = require("mysql2/promise"); const ALTER_OPERATIONS = [ { table: "ai_providers", description: "添加 visibility 列(public/private 可见性)", sql: [ "ALTER TABLE `ai_providers` ADD COLUMN `visibility` ENUM('public','private') NOT NULL DEFAULT 'private' AFTER `is_default`", "ALTER TABLE `ai_providers` ADD INDEX `ai_provider_visibility_idx` (`visibility`)", "ALTER TABLE `ai_providers` ADD INDEX `ai_provider_created_by_idx` (`created_by`)", ], }, ]; async function columnExists(conn, table, column) { const [rows] = await conn.execute( `SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?`, [table, column], ); return rows.length > 0; } async function indexExists(conn, table, indexName) { const [rows] = await conn.execute( `SELECT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ? LIMIT 1`, [table, indexName], ); return rows.length > 0; } async function main() { const conn = await mysql.createConnection({ uri: process.env.DATABASE_URL }); console.log("✅ 已连接数据库\n"); for (const op of ALTER_OPERATIONS) { console.log(`${"=".repeat(60)}`); console.log(`表: ${op.table} — ${op.description}`); console.log("=".repeat(60)); for (const sql of op.sql) { try { const isColumn = sql.includes("ADD COLUMN"); const isIndex = sql.includes("ADD INDEX"); if (isColumn) { const match = sql.match(/ADD COLUMN `(\w+)`/); if (match && await columnExists(conn, op.table, match[1])) { console.log(` ⏭️ 跳过已存在的列: ${match[1]}`); continue; } } else if (isIndex) { const match = sql.match(/ADD INDEX `(\w+)`/); if (match && await indexExists(conn, op.table, match[1])) { console.log(` ⏭️ 跳过已存在的索引: ${match[1]}`); continue; } } await conn.execute(sql); console.log(` ✅ 执行成功: ${sql.slice(0, 100)}...`); } catch (err) { if (err.code === "ER_DUP_FIELDNAME" || err.code === "ER_DUP_KEYNAME" || err.errno === 1060 || err.errno === 1061) { console.log(` ⏭️ 已存在,跳过: ${err.message}`); } else { console.error(` ❌ 执行失败: ${err.message}`); console.error(` SQL: ${sql}`); } } } console.log(""); } // 验证最终列结构 console.log(`${"=".repeat(60)}`); console.log("最终验证 — 检查 visibility 列是否已添加"); console.log("=".repeat(60)); const checks = [ ["ai_providers", "visibility"], ]; let allOk = true; for (const [table, col] of checks) { const exists = await columnExists(conn, table, col); const status = exists ? "✅" : "❌"; if (!exists) allOk = false; console.log(` ${status} ${table}.${col}`); } console.log(allOk ? "\n✅ visibility 列已就绪" : "\n❌ 仍有缺失列,请检查错误"); await conn.end(); process.exitCode = allOk ? 0 : 1; } main().catch((err) => { console.error("致命错误:", err); process.exit(1); });