run_migration.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. """数据库迁移脚本"""
  2. import os
  3. import psycopg2
  4. from dotenv import load_dotenv
  5. load_dotenv()
  6. # 数据库连接配置
  7. DB_HOST = os.getenv("DB_HOST", "8.156.90.138")
  8. DB_PORT = os.getenv("DB_PORT", "5432")
  9. DB_USER = os.getenv("DB_USER", "AigcSpace_test")
  10. DB_PASSWORD = os.getenv("DB_PASSWORD", "pnk6FrPzYKmwHHjM")
  11. DB_NAME = os.getenv("DB_NAME", "aigcspace_test")
  12. def run_migration():
  13. """执行数据库迁移"""
  14. conn = psycopg2.connect(
  15. host=DB_HOST,
  16. port=DB_PORT,
  17. user=DB_USER,
  18. password=DB_PASSWORD,
  19. dbname=DB_NAME
  20. )
  21. conn.autocommit = False
  22. cur = conn.cursor()
  23. try:
  24. print("开始执行数据库迁移...")
  25. # ============================================
  26. # 1. models_new 表添加缺失字段
  27. # ============================================
  28. migrations = [
  29. # 添加缺失字段
  30. ("ALTER TABLE aigcspace.models_new ADD COLUMN IF NOT EXISTS group_name VARCHAR(100)", "添加 group_name 字段"),
  31. ("ALTER TABLE aigcspace.models_new ADD COLUMN IF NOT EXISTS encrypted_api_key TEXT", "添加 encrypted_api_key 字段"),
  32. ("ALTER TABLE aigcspace.models_new ADD COLUMN IF NOT EXISTS is_thinking BOOLEAN NOT NULL DEFAULT FALSE", "添加 is_thinking 字段"),
  33. ("ALTER TABLE aigcspace.models_new ADD COLUMN IF NOT EXISTS source_keys TEXT[]", "添加 source_keys 字段"),
  34. ("ALTER TABLE aigcspace.models_new ADD COLUMN IF NOT EXISTS normalized_keys TEXT[]", "添加 normalized_keys 字段"),
  35. ("ALTER TABLE aigcspace.models_new ADD COLUMN IF NOT EXISTS is_local BOOLEAN NOT NULL DEFAULT FALSE", "添加 is_local 字段"),
  36. ("ALTER TABLE aigcspace.models_new ADD COLUMN IF NOT EXISTS user_id VARCHAR(50)", "添加 user_id 字段"),
  37. ("ALTER TABLE aigcspace.models_new ADD COLUMN IF NOT EXISTS base_url VARCHAR(500)", "添加 base_url 字段"),
  38. ("ALTER TABLE aigcspace.models_new ADD COLUMN IF NOT EXISTS local_api_key VARCHAR(500)", "添加 local_api_key 字段"),
  39. ("ALTER TABLE aigcspace.models_new ADD COLUMN IF NOT EXISTS visibility VARCHAR(20) DEFAULT 'user'", "添加 visibility 字段"),
  40. # 添加索引
  41. ("CREATE INDEX IF NOT EXISTS idx_models_new_is_local ON aigcspace.models_new(is_local)", "创建 is_local 索引"),
  42. ("CREATE INDEX IF NOT EXISTS idx_models_new_group_name ON aigcspace.models_new(group_name)", "创建 group_name 索引"),
  43. ]
  44. for sql, desc in migrations:
  45. try:
  46. cur.execute(sql)
  47. print(f"✓ {desc}")
  48. except Exception as e:
  49. if "duplicate" in str(e).lower() or "already exists" in str(e).lower():
  50. print(f"○ {desc} (已存在,跳过)")
  51. else:
  52. print(f"✗ {desc}: {e}")
  53. # ============================================
  54. # 2. 创建可能缺失的表
  55. # ============================================
  56. tables = [
  57. # asr_recognition 表
  58. """CREATE TABLE IF NOT EXISTS aigcspace.asr_recognition (
  59. id SERIAL PRIMARY KEY,
  60. user_id VARCHAR(50) NOT NULL REFERENCES aigcspace.users(id) ON DELETE CASCADE,
  61. model VARCHAR(100) NOT NULL,
  62. audio_url VARCHAR(500),
  63. audio_base64 TEXT,
  64. language VARCHAR(20),
  65. enable_itn BOOLEAN DEFAULT FALSE,
  66. context TEXT,
  67. result_text TEXT NOT NULL,
  68. detected_language VARCHAR(20),
  69. emotion VARCHAR(20),
  70. duration INTEGER,
  71. input_tokens INTEGER DEFAULT 0,
  72. output_tokens INTEGER DEFAULT 0,
  73. bill NUMERIC(10,4) DEFAULT 0,
  74. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  75. )""",
  76. # asr_recognition_v2 表
  77. """CREATE TABLE IF NOT EXISTS aigcspace.asr_recognition_v2 (
  78. id SERIAL PRIMARY KEY,
  79. user_id VARCHAR(50) NOT NULL REFERENCES aigcspace.users(id) ON DELETE CASCADE,
  80. task_id VARCHAR(100) UNIQUE NOT NULL,
  81. model VARCHAR(100) NOT NULL,
  82. file_url VARCHAR(500) NOT NULL,
  83. status VARCHAR(20) DEFAULT 'PENDING',
  84. result_text TEXT,
  85. result_url VARCHAR(500),
  86. duration INTEGER,
  87. bill NUMERIC(10,4) DEFAULT 0,
  88. error_message TEXT,
  89. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  90. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  91. completed_at TIMESTAMP
  92. )""",
  93. # audio_synthesis_v2 表
  94. """CREATE TABLE IF NOT EXISTS aigcspace.audio_synthesis_v2 (
  95. id SERIAL PRIMARY KEY,
  96. user_id VARCHAR(50) NOT NULL REFERENCES aigcspace.users(id) ON DELETE CASCADE,
  97. task_id VARCHAR(100) UNIQUE NOT NULL,
  98. model VARCHAR(100) NOT NULL,
  99. voice VARCHAR(100) NOT NULL,
  100. text TEXT NOT NULL,
  101. audio_url VARCHAR(500),
  102. status VARCHAR(20) DEFAULT 'PENDING',
  103. duration NUMERIC(10,2),
  104. format VARCHAR(20) DEFAULT 'mp3',
  105. characters INTEGER,
  106. bill NUMERIC(10,4) DEFAULT 0,
  107. custom_name VARCHAR(200),
  108. error_message TEXT,
  109. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  110. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  111. completed_at TIMESTAMP
  112. )""",
  113. # voice_clone_v2 表
  114. """CREATE TABLE IF NOT EXISTS aigcspace.voice_clone_v2 (
  115. id SERIAL PRIMARY KEY,
  116. user_id VARCHAR(50) NOT NULL REFERENCES aigcspace.users(id) ON DELETE CASCADE,
  117. task_id VARCHAR(100) UNIQUE NOT NULL,
  118. voice_id VARCHAR(200),
  119. target_model VARCHAR(100) NOT NULL,
  120. prefix VARCHAR(20) NOT NULL,
  121. voice_name VARCHAR(50),
  122. audio_url VARCHAR(500),
  123. status VARCHAR(20) DEFAULT 'PENDING',
  124. bill NUMERIC(10,4) DEFAULT 0,
  125. error_message TEXT,
  126. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  127. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  128. completed_at TIMESTAMP
  129. )""",
  130. # long_text_audio 表
  131. """CREATE TABLE IF NOT EXISTS aigcspace.long_text_audio (
  132. id SERIAL PRIMARY KEY,
  133. user_id VARCHAR(50) NOT NULL REFERENCES aigcspace.users(id) ON DELETE CASCADE,
  134. task_id VARCHAR(100) UNIQUE NOT NULL,
  135. model VARCHAR(100) NOT NULL,
  136. voice VARCHAR(100) NOT NULL,
  137. text TEXT NOT NULL,
  138. text_length INTEGER NOT NULL,
  139. segment_count INTEGER DEFAULT 0,
  140. segments JSONB DEFAULT '[]',
  141. audio_url VARCHAR(500),
  142. status VARCHAR(20) DEFAULT 'PENDING',
  143. progress INTEGER DEFAULT 0,
  144. duration NUMERIC(10,2),
  145. format VARCHAR(20) DEFAULT 'mp3',
  146. bill NUMERIC(10,4) DEFAULT 0,
  147. custom_name VARCHAR(200),
  148. error_message TEXT,
  149. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  150. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  151. completed_at TIMESTAMP
  152. )""",
  153. # system_voice 表
  154. """CREATE TABLE IF NOT EXISTS aigcspace.system_voice (
  155. id SERIAL PRIMARY KEY,
  156. voice_id VARCHAR(100) UNIQUE NOT NULL,
  157. name VARCHAR(50) NOT NULL,
  158. trait VARCHAR(100),
  159. age VARCHAR(20),
  160. category VARCHAR(50),
  161. languages JSONB DEFAULT '[]',
  162. models JSONB DEFAULT '[]',
  163. ssml_support BOOLEAN DEFAULT FALSE,
  164. instruct_support BOOLEAN DEFAULT FALSE,
  165. timestamp_support BOOLEAN DEFAULT FALSE,
  166. is_active BOOLEAN DEFAULT TRUE,
  167. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  168. )""",
  169. # model_price_new 表
  170. """CREATE TABLE IF NOT EXISTS aigcspace.model_price_new (
  171. id SERIAL PRIMARY KEY,
  172. model_code VARCHAR(200) NOT NULL,
  173. label VARCHAR(200) NOT NULL,
  174. tier_min NUMERIC(20,2),
  175. tier_max NUMERIC(20,2),
  176. tier_unit VARCHAR(50),
  177. input_price_original NUMERIC(20,8) NOT NULL DEFAULT 0,
  178. output_price_original NUMERIC(20,8) NOT NULL DEFAULT 0,
  179. discount_rate NUMERIC(5,4) NOT NULL DEFAULT 1,
  180. discount_label VARCHAR(20),
  181. input_price_discounted NUMERIC(20,8) NOT NULL DEFAULT 0,
  182. output_price_discounted NUMERIC(20,8) NOT NULL DEFAULT 0,
  183. currency VARCHAR(10) NOT NULL DEFAULT 'CNY',
  184. unit VARCHAR(100) NOT NULL,
  185. display_multiplier INTEGER NOT NULL DEFAULT 1,
  186. source_url TEXT,
  187. crawled_at TIMESTAMP NOT NULL,
  188. is_active BOOLEAN NOT NULL DEFAULT TRUE,
  189. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  190. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  191. )""",
  192. # crawler_sync_log 表
  193. """CREATE TABLE IF NOT EXISTS aigcspace.crawler_sync_log (
  194. id SERIAL PRIMARY KEY,
  195. crawler_version INTEGER NOT NULL,
  196. synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  197. model_count INTEGER NOT NULL DEFAULT 0,
  198. price_count INTEGER NOT NULL DEFAULT 0,
  199. status VARCHAR(20) NOT NULL DEFAULT 'success',
  200. error_message TEXT
  201. )""",
  202. # user_local_model_permission 表
  203. """CREATE TABLE IF NOT EXISTS aigcspace.user_local_model_permission (
  204. id SERIAL PRIMARY KEY,
  205. user_id VARCHAR(50) NOT NULL REFERENCES aigcspace.users(id) ON DELETE CASCADE,
  206. model_id INTEGER NOT NULL,
  207. granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  208. granted_by VARCHAR(50),
  209. UNIQUE(user_id, model_id)
  210. )""",
  211. ]
  212. print("\n创建缺失的表...")
  213. for sql in tables:
  214. table_name = sql.split("aigcspace.")[1].split(" ")[0] if "aigcspace." in sql else "unknown"
  215. try:
  216. cur.execute(sql)
  217. print(f"✓ 表 {table_name} 就绪")
  218. except Exception as e:
  219. if "already exists" in str(e).lower():
  220. print(f"○ 表 {table_name} 已存在,跳过")
  221. else:
  222. print(f"✗ 表 {table_name}: {e}")
  223. # 提交事务
  224. conn.commit()
  225. print("\n✓ 迁移完成!")
  226. # 验证
  227. print("\n验证表结构...")
  228. cur.execute("""
  229. SELECT table_name FROM information_schema.tables
  230. WHERE table_schema = 'aigcspace' ORDER BY table_name
  231. """)
  232. tables = cur.fetchall()
  233. print(f"数据库中共有 {len(tables)} 张表:")
  234. for t in tables:
  235. print(f" - {t[0]}")
  236. except Exception as e:
  237. conn.rollback()
  238. print(f"\n✗ 迁移失败: {e}")
  239. raise
  240. finally:
  241. cur.close()
  242. conn.close()
  243. if __name__ == "__main__":
  244. run_migration()