Browse Source

修复DatabaseInitConfig数据库初始化遗漏问题

dev-mcp
wanghanlin 4 weeks ago
parent
commit
e2ff2a9c3d
  1. 254
      src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java

254
src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java

@ -27,152 +27,207 @@ public class DatabaseInitConfig {
@PostConstruct @PostConstruct
public void init() { public void init() {
try {
// 检查 chat_message 表是否存在
boolean chatMessageTableExists = checkTableExists("chat_message");
if (!chatMessageTableExists) {
log.info("创建聊天消息表 chat_message");
log.info("========== 数据库初始化开始 ==========");
// ==================== 核心业务表 ====================
safeInit("创建聊天消息表 chat_message", () -> {
if (!checkTableExists("chat_message")) {
createChatMessageTable(); createChatMessageTable();
} }
});
// 检查 knowledge_category 表是否存在
boolean categoryTableExists = checkTableExists("knowledge_category");
if (!categoryTableExists) {
log.info("创建知识库分类表 knowledge_category");
safeInit("创建知识库分类表 knowledge_category", () -> {
if (!checkTableExists("knowledge_category")) {
createCategoryTable(); createCategoryTable();
} }
});
// 检查 knowledge_document 表是否存在
boolean documentTableExists = checkTableExists("knowledge_document");
if (!documentTableExists) {
log.info("创建知识文档表 knowledge_document");
safeInit("创建知识文档表 knowledge_document", () -> {
if (!checkTableExists("knowledge_document")) {
createDocumentTable(); createDocumentTable();
} else { } else {
// 修复已存在表的 tags 默认值从数组改为对象
fixTagsDefaultValue(); fixTagsDefaultValue();
// 自动添加 content_hash 二期新增
addContentHashColumn();
// P1-2.1: 自动添加 enabled 文档启用/禁用
addDocumentEnabledColumn();
// P1-2.5: 自动添加 extra_config per-doc 分块参数
addDocumentExtraConfigColumn();
} }
boolean roleTableExists = checkTableExists("customer_service_role");
if (!roleTableExists) {
log.info("创建客服角色表 customer_service_role");
});
// 以下迁移方法始终执行幂等确保新旧环境都拥有所有列
safeInit("迁移 knowledge_document.content_hash 列", this::addContentHashColumn);
safeInit("迁移 knowledge_document.enabled 列", this::addDocumentEnabledColumn);
safeInit("迁移 knowledge_document.extra_config 列", this::addDocumentExtraConfigColumn);
safeInit("创建客服角色表 customer_service_role", () -> {
if (!checkTableExists("customer_service_role")) {
createCustomerServiceRoleTable(); createCustomerServiceRoleTable();
} else { } else {
// 清理已废弃的 model per-role 模型功能已撤除模型统一由模型配置中心管理
dropRoleModelColumn(); dropRoleModelColumn();
} }
});
boolean roleCategoryTableExists = checkTableExists("customer_service_role_category");
if (!roleCategoryTableExists) {
log.info("创建客服角色知识库关联表 customer_service_role_category");
safeInit("创建客服角色知识库关联表 customer_service_role_category", () -> {
if (!checkTableExists("customer_service_role_category")) {
createCustomerServiceRoleCategoryTable(); createCustomerServiceRoleCategoryTable();
} }
});
boolean accountTableExists = checkTableExists("customer_account");
if (!accountTableExists) {
log.info("创建客服账号表 customer_account");
safeInit("创建客服账号表 customer_account", () -> {
if (!checkTableExists("customer_account")) {
createCustomerAccountTable(); createCustomerAccountTable();
} }
});
boolean conversationSessionTableExists = checkTableExists("conversation_session");
if (!conversationSessionTableExists) {
log.info("创建会话归属表 conversation_session");
safeInit("创建会话归属表 conversation_session", () -> {
if (!checkTableExists("conversation_session")) {
createConversationSessionTable(); createConversationSessionTable();
} }
addConversationSessionExternalAccountIdColumn();
syncDefaultCustomerServiceRoles();
syncDefaultCustomerAccounts();
});
safeInit("迁移 conversation_session.external_account_id 列", this::addConversationSessionExternalAccountIdColumn);
safeInit("同步默认客服角色", this::syncDefaultCustomerServiceRoles);
safeInit("同步默认客服账号", this::syncDefaultCustomerAccounts);
// ==================== AI 模型配置表 ====================
safeInit("创建 AI 模型配置表 ai_model_config", () -> {
if (!checkTableExists("ai_model_config")) {
createAiModelConfigTable();
}
});
// ==================== P0 阶段新增表 ====================
// ==================== P0 阶段新增表 ====================
// P0-004: 内容安全过滤
// P0-004: 内容安全过滤
safeInit("创建敏感词表 sensitive_word", () -> {
if (!checkTableExists("sensitive_word")) { if (!checkTableExists("sensitive_word")) {
log.info("创建敏感词表 sensitive_word");
createSensitiveWordTable(); createSensitiveWordTable();
} }
});
safeInit("创建内容审计日志表 content_audit_log", () -> {
if (!checkTableExists("content_audit_log")) { if (!checkTableExists("content_audit_log")) {
log.info("创建内容审计日志表 content_audit_log");
createContentAuditLogTable(); createContentAuditLogTable();
} }
});
// P0-002: 用户反馈
// P0-002: 用户反馈
safeInit("创建消息反馈表 message_feedback", () -> {
if (!checkTableExists("message_feedback")) { if (!checkTableExists("message_feedback")) {
log.info("创建消息反馈表 message_feedback");
createMessageFeedbackTable(); createMessageFeedbackTable();
} }
});
// P0-003: FAQ 知识库
// P0-003: FAQ 知识库
safeInit("创建 FAQ 知识库表 knowledge_faq", () -> {
if (!checkTableExists("knowledge_faq")) { if (!checkTableExists("knowledge_faq")) {
log.info("创建 FAQ 知识库表 knowledge_faq");
createKnowledgeFaqTable(); createKnowledgeFaqTable();
} else {
// 自动添加 category_id 引用文档管理分类
addFaqCategoryIdColumn();
} }
});
safeInit("迁移 knowledge_faq.category_id 列", this::addFaqCategoryIdColumn);
safeInit("创建 FAQ 向量索引表 faq_embedding", () -> {
if (!checkTableExists("faq_embedding")) { if (!checkTableExists("faq_embedding")) {
log.info("创建 FAQ 向量索引表 faq_embedding");
createFaqEmbeddingTable(); createFaqEmbeddingTable();
} }
});
// ==================== P1 阶段新增表 ====================
// ==================== P1 阶段新增表 ====================
// P1-001: 用户认证与多用户管理
// P1-001: 用户认证与多用户管理
safeInit("创建系统用户表 sys_user", () -> {
if (!checkTableExists("sys_user")) { if (!checkTableExists("sys_user")) {
log.info("创建系统用户表 sys_user");
createSysUserTable(); createSysUserTable();
} }
});
safeInit("创建系统角色表 sys_role", () -> {
if (!checkTableExists("sys_role")) { if (!checkTableExists("sys_role")) {
log.info("创建系统角色表 sys_role");
createSysRoleTable(); createSysRoleTable();
} }
});
safeInit("创建系统权限表 sys_permission", () -> {
if (!checkTableExists("sys_permission")) { if (!checkTableExists("sys_permission")) {
log.info("创建系统权限表 sys_permission");
createSysPermissionTable(); createSysPermissionTable();
} }
});
safeInit("创建用户角色关联表 sys_user_role", () -> {
if (!checkTableExists("sys_user_role")) { if (!checkTableExists("sys_user_role")) {
log.info("创建用户角色关联表 sys_user_role");
createSysUserRoleTable(); createSysUserRoleTable();
} }
// chat_message 增加 user_id 数据隔离
addChatMessageUserIdColumn();
// 同步默认角色和管理员账号
syncDefaultSysRoles();
syncDefaultAdminUser();
});
safeInit("迁移 chat_message.user_id 列", this::addChatMessageUserIdColumn);
safeInit("同步默认系统角色", this::syncDefaultSysRoles);
safeInit("同步默认管理员账号", this::syncDefaultAdminUser);
// P1-002: 运营数据分析看板
// P1-002: 运营数据分析看板
safeInit("创建 RAG 命中记录表 rag_hit_log", () -> {
if (!checkTableExists("rag_hit_log")) { if (!checkTableExists("rag_hit_log")) {
log.info("创建 RAG 命中记录表 rag_hit_log");
createRagHitLogTable(); createRagHitLogTable();
} }
});
safeInit("创建看板快照表 dashboard_snapshot", () -> {
if (!checkTableExists("dashboard_snapshot")) { if (!checkTableExists("dashboard_snapshot")) {
log.info("创建看板快照表 dashboard_snapshot");
createDashboardSnapshotTable(); createDashboardSnapshotTable();
} }
});
// P1-003: API 开放平台
// P1-003: API 开放平台
safeInit("创建 API Key 表 api_key", () -> {
if (!checkTableExists("api_key")) { if (!checkTableExists("api_key")) {
log.info("创建 API Key 表 api_key");
createApiKeyTable(); createApiKeyTable();
} }
});
safeInit("创建 Webhook 配置表 webhook_config", () -> {
if (!checkTableExists("webhook_config")) { if (!checkTableExists("webhook_config")) {
log.info("创建 Webhook 配置表 webhook_config");
createWebhookConfigTable(); createWebhookConfigTable();
} }
});
// P0-001: 混合检索 - vector_store 添加全文检索列
safeInit("初始化 vector_store 全文检索", this::initVectorStoreFullTextSearch);
// P0-001: 混合检索 - vector_store 添加全文检索列
initVectorStoreFullTextSearch();
// 为所有表添加注释幂等可重复执行
safeInit("应用数据库表注释", this::applyTableComments);
// 为所有表添加注释幂等可重复执行
applyTableComments();
// ==================== 初始化结果验证 ====================
verifyInitialization();
log.info("========== 数据库初始化完成 ==========");
}
log.info("数据库初始化完成");
/**
* 安全执行初始化步骤单个步骤失败不影响其他步骤
*/
private void safeInit(String description, Runnable action) {
try {
action.run();
} catch (Exception e) { } catch (Exception e) {
log.error("数据库初始化失败", e);
log.error("数据库初始化步骤失败 [{}]: {}", description, e.getMessage(), e);
}
}
/**
* 初始化完成后验证所有预期的表是否存在汇总报告缺失表
*/
private void verifyInitialization() {
String[] expectedTables = {
"chat_message", "knowledge_category", "knowledge_document",
"customer_service_role", "customer_service_role_category",
"customer_account", "conversation_session", "ai_model_config",
"sensitive_word", "content_audit_log", "message_feedback",
"knowledge_faq", "faq_embedding",
"sys_user", "sys_role", "sys_permission", "sys_user_role",
"rag_hit_log", "dashboard_snapshot",
"api_key", "webhook_config"
};
java.util.List<String> missingTables = new java.util.ArrayList<>();
for (String table : expectedTables) {
if (!checkTableExists(table)) {
missingTables.add(table);
}
}
if (missingTables.isEmpty()) {
log.info("数据库初始化验证通过,所有 {} 张表均已创建", expectedTables.length);
} else {
log.error("⚠️ 数据库初始化验证失败!以下 {} 张表缺失: {}", missingTables.size(), missingTables);
log.error("请检查上方错误日志,修复后重启应用");
} }
} }
@ -330,6 +385,32 @@ public class DatabaseInitConfig {
} }
private void createAiModelConfigTable() {
String sql = """
CREATE TABLE IF NOT EXISTS ai_model_config (
id BIGINT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
app_type VARCHAR(32) NOT NULL,
provider VARCHAR(64) NOT NULL,
api_key VARCHAR(256),
model_name VARCHAR(128) NOT NULL,
temperature DOUBLE PRECISION DEFAULT 0.7,
max_tokens INTEGER DEFAULT 2048,
base_url VARCHAR(256),
extra_config JSONB DEFAULT '{}' NOT NULL,
is_active BOOLEAN DEFAULT FALSE NOT NULL,
priority INTEGER DEFAULT 0 NOT NULL,
description VARCHAR(500),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
is_delete BOOLEAN NOT NULL DEFAULT FALSE
)
""";
jdbcTemplate.execute(sql);
jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_ai_model_config_app_type ON ai_model_config (app_type)");
jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_ai_model_config_active ON ai_model_config (is_active) WHERE is_delete = FALSE");
}
private void addConversationSessionExternalAccountIdColumn() { private void addConversationSessionExternalAccountIdColumn() {
try { try {
String checkSql = "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'conversation_session' AND column_name = 'external_account_id'"; String checkSql = "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'conversation_session' AND column_name = 'external_account_id'";
@ -459,7 +540,7 @@ public class DatabaseInitConfig {
jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_knowledge_document_content_hash ON knowledge_document (content_hash)"); jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_knowledge_document_content_hash ON knowledge_document (content_hash)");
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("添加 content_hash 列时出错", e);
log.error("添加 knowledge_document.content_hash 列失败,请手动执行: ALTER TABLE knowledge_document ADD COLUMN content_hash VARCHAR(64)", e);
} }
} }
@ -477,7 +558,7 @@ public class DatabaseInitConfig {
jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_knowledge_document_enabled ON knowledge_document (enabled)"); jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_knowledge_document_enabled ON knowledge_document (enabled)");
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("添加 knowledge_document.enabled 列时出错", e);
log.error("添加 knowledge_document.enabled 列失败,请手动执行: ALTER TABLE knowledge_document ADD COLUMN enabled BOOLEAN DEFAULT TRUE NOT NULL", e);
} }
} }
@ -494,7 +575,7 @@ public class DatabaseInitConfig {
jdbcTemplate.execute("ALTER TABLE knowledge_document ADD COLUMN extra_config JSONB DEFAULT '{}'"); jdbcTemplate.execute("ALTER TABLE knowledge_document ADD COLUMN extra_config JSONB DEFAULT '{}'");
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("添加 knowledge_document.extra_config 列时出错", e);
log.error("添加 knowledge_document.extra_config 列失败,请手动执行: ALTER TABLE knowledge_document ADD COLUMN extra_config JSONB DEFAULT '{}'", e);
} }
} }
@ -613,7 +694,7 @@ public class DatabaseInitConfig {
jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_faq_category_id ON knowledge_faq (category_id)"); jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_faq_category_id ON knowledge_faq (category_id)");
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("添加 knowledge_faq.category_id 列时出错", e);
log.error("添加 knowledge_faq.category_id 列失败,请手动执行: ALTER TABLE knowledge_faq ADD COLUMN category_id BIGINT", e);
} }
} }
@ -755,7 +836,7 @@ public class DatabaseInitConfig {
jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_chat_message_user_id ON chat_message (user_id)"); jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_chat_message_user_id ON chat_message (user_id)");
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("添加 user_id 列时出错", e);
log.error("添加 chat_message.user_id 列失败,请手动执行: ALTER TABLE chat_message ADD COLUMN user_id BIGINT", e);
} }
} }
@ -1093,6 +1174,25 @@ public class DatabaseInitConfig {
// ===== webhook_config ===== // ===== webhook_config =====
executeComment("TABLE webhook_config", "Webhook 配置表(事件推送订阅)"); executeComment("TABLE webhook_config", "Webhook 配置表(事件推送订阅)");
// ===== ai_model_config =====
executeComment("TABLE ai_model_config", "AI 大模型配置表(管理多套模型配置,按应用类型绑定)");
executeComment("COLUMN ai_model_config.id", "主键(雪花算法生成)");
executeComment("COLUMN ai_model_config.name", "配置名称");
executeComment("COLUMN ai_model_config.app_type", "应用类型: CHAT / EMBEDDING / RAG_REWRITE / RERANK");
executeComment("COLUMN ai_model_config.provider", "模型提供商: dashscope / openai / deepseek / moonshot / zhipu / volcengine 等");
executeComment("COLUMN ai_model_config.api_key", "API Key(数据库加密存储,前端脱敏展示)");
executeComment("COLUMN ai_model_config.model_name", "模型名称(如 qwen-turbo、gpt-4o)");
executeComment("COLUMN ai_model_config.temperature", "温度参数(控制输出随机性,0.0~2.0)");
executeComment("COLUMN ai_model_config.max_tokens", "最大 Token 数(单次生成上限)");
executeComment("COLUMN ai_model_config.base_url", "API 基础地址(可选,用于私有化部署或第三方厂商)");
executeComment("COLUMN ai_model_config.extra_config", "扩展配置(JSONB,存储 topP、dimensions 等自定义参数)");
executeComment("COLUMN ai_model_config.is_active", "是否激活(每种 App 类型只能有一个激活配置)");
executeComment("COLUMN ai_model_config.priority", "优先级(数值越大越优先,多套配置时生效)");
executeComment("COLUMN ai_model_config.description", "配置描述说明");
executeComment("COLUMN ai_model_config.create_time", "创建时间");
executeComment("COLUMN ai_model_config.update_time", "更新时间");
executeComment("COLUMN ai_model_config.is_delete", "逻辑删除: FALSE=正常 TRUE=已删除");
// ===== chat_message.user_id ===== // ===== chat_message.user_id =====
executeComment("COLUMN chat_message.user_id", "所属系统用户ID(数据隔离,SDK调用时为null)"); executeComment("COLUMN chat_message.user_id", "所属系统用户ID(数据隔离,SDK调用时为null)");

Loading…
Cancel
Save