simple_init_db.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. #!/usr/bin/env python3
  2. """
  3. 简化的数据库初始化脚本
  4. """
  5. import sys
  6. import os
  7. # 添加项目根目录到Python路径
  8. sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
  9. # 加载环境变量
  10. from dotenv import load_dotenv
  11. load_dotenv()
  12. import pymysql
  13. from urllib.parse import urlparse
  14. import uuid
  15. from datetime import datetime
  16. def get_db_config():
  17. """获取数据库配置"""
  18. database_url = os.getenv('DATABASE_URL', '')
  19. parsed = urlparse(database_url)
  20. return {
  21. 'host': parsed.hostname or 'localhost',
  22. 'port': parsed.port or 3306,
  23. 'user': parsed.username or 'root',
  24. 'password': parsed.password or '',
  25. 'database': parsed.path[1:] if parsed.path else 'sso_db',
  26. 'charset': 'utf8mb4'
  27. }
  28. def hash_password_simple(password):
  29. """简单的密码哈希"""
  30. import hashlib
  31. import secrets
  32. # 生成盐值
  33. salt = secrets.token_hex(16)
  34. # 使用SHA256哈希
  35. password_hash = hashlib.sha256((password + salt).encode()).hexdigest()
  36. return f"sha256${salt}${password_hash}"
  37. def generate_random_string(length=32):
  38. """生成随机字符串"""
  39. import secrets
  40. import string
  41. alphabet = string.ascii_letters + string.digits
  42. return ''.join(secrets.choice(alphabet) for _ in range(length))
  43. def create_tables(connection):
  44. """创建数据库表"""
  45. cursor = connection.cursor()
  46. # 用户表
  47. cursor.execute("""
  48. CREATE TABLE IF NOT EXISTS users (
  49. id VARCHAR(36) PRIMARY KEY COMMENT '用户ID',
  50. username VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名',
  51. email VARCHAR(100) UNIQUE NOT NULL COMMENT '邮箱',
  52. phone VARCHAR(20) UNIQUE COMMENT '手机号',
  53. password_hash VARCHAR(255) NOT NULL COMMENT '密码哈希',
  54. avatar_url VARCHAR(500) COMMENT '头像URL',
  55. is_active BOOLEAN DEFAULT TRUE COMMENT '是否激活',
  56. is_superuser BOOLEAN DEFAULT FALSE COMMENT '是否超级管理员',
  57. last_login_at TIMESTAMP NULL COMMENT '最后登录时间',
  58. last_login_ip VARCHAR(45) COMMENT '最后登录IP',
  59. failed_login_attempts INT DEFAULT 0 COMMENT '失败登录次数',
  60. locked_until TIMESTAMP NULL COMMENT '锁定直到时间',
  61. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  62. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  63. is_deleted BOOLEAN DEFAULT FALSE COMMENT '是否删除',
  64. INDEX idx_username (username),
  65. INDEX idx_email (email),
  66. INDEX idx_phone (phone),
  67. INDEX idx_created_at (created_at)
  68. ) COMMENT='用户表'
  69. """)
  70. # 应用表
  71. cursor.execute("""
  72. CREATE TABLE IF NOT EXISTS apps (
  73. id VARCHAR(36) PRIMARY KEY COMMENT '应用ID',
  74. name VARCHAR(100) NOT NULL COMMENT '应用名称',
  75. app_key VARCHAR(100) UNIQUE NOT NULL COMMENT '应用Key',
  76. app_secret VARCHAR(255) NOT NULL COMMENT '应用Secret',
  77. description TEXT COMMENT '应用描述',
  78. icon_url VARCHAR(500) COMMENT '应用图标',
  79. redirect_uris JSON NOT NULL COMMENT '回调URL列表',
  80. scope JSON COMMENT '权限范围',
  81. is_active BOOLEAN DEFAULT TRUE COMMENT '是否激活',
  82. is_trusted BOOLEAN DEFAULT FALSE COMMENT '是否受信任应用',
  83. access_token_expires INT DEFAULT 7200 COMMENT '访问令牌过期时间(秒)',
  84. refresh_token_expires INT DEFAULT 2592000 COMMENT '刷新令牌过期时间(秒)',
  85. created_by VARCHAR(36) COMMENT '创建者ID',
  86. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  87. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  88. is_deleted BOOLEAN DEFAULT FALSE COMMENT '是否删除',
  89. INDEX idx_app_key (app_key),
  90. INDEX idx_created_at (created_at)
  91. ) COMMENT='应用表'
  92. """)
  93. # 访问令牌表
  94. cursor.execute("""
  95. CREATE TABLE IF NOT EXISTS oauth_access_tokens (
  96. id VARCHAR(36) PRIMARY KEY COMMENT '令牌ID',
  97. user_id VARCHAR(36) NOT NULL COMMENT '用户ID',
  98. app_id VARCHAR(36) COMMENT '应用ID',
  99. token VARCHAR(512) UNIQUE NOT NULL COMMENT '访问令牌',
  100. refresh_token VARCHAR(512) UNIQUE COMMENT '刷新令牌',
  101. token_type VARCHAR(50) DEFAULT 'Bearer' COMMENT '令牌类型',
  102. scope VARCHAR(500) COMMENT '权限范围',
  103. expires_at TIMESTAMP NOT NULL COMMENT '过期时间',
  104. revoked BOOLEAN DEFAULT FALSE COMMENT '是否撤销',
  105. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  106. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  107. last_used_at TIMESTAMP NULL COMMENT '最后使用时间',
  108. is_deleted BOOLEAN DEFAULT FALSE COMMENT '是否删除',
  109. INDEX idx_token (token),
  110. INDEX idx_refresh_token (refresh_token),
  111. INDEX idx_user_app (user_id, app_id),
  112. INDEX idx_expires_at (expires_at)
  113. ) COMMENT='访问令牌表'
  114. """)
  115. # 授权码表
  116. cursor.execute("""
  117. CREATE TABLE IF NOT EXISTS oauth_authorization_codes (
  118. id VARCHAR(36) PRIMARY KEY COMMENT '授权码ID',
  119. user_id VARCHAR(36) NOT NULL COMMENT '用户ID',
  120. app_id VARCHAR(36) NOT NULL COMMENT '应用ID',
  121. code VARCHAR(100) UNIQUE NOT NULL COMMENT '授权码',
  122. redirect_uri VARCHAR(500) NOT NULL COMMENT '回调URL',
  123. scope VARCHAR(500) COMMENT '权限范围',
  124. expires_at TIMESTAMP NOT NULL COMMENT '过期时间',
  125. used BOOLEAN DEFAULT FALSE COMMENT '是否已使用',
  126. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  127. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  128. is_deleted BOOLEAN DEFAULT FALSE COMMENT '是否删除',
  129. INDEX idx_code (code),
  130. INDEX idx_user_app (user_id, app_id),
  131. INDEX idx_expires_at (expires_at)
  132. ) COMMENT='授权码表'
  133. """)
  134. # 登录日志表
  135. cursor.execute("""
  136. CREATE TABLE IF NOT EXISTS login_logs (
  137. id VARCHAR(36) PRIMARY KEY COMMENT '日志ID',
  138. user_id VARCHAR(36) COMMENT '用户ID',
  139. username VARCHAR(50) NOT NULL COMMENT '用户名',
  140. login_type VARCHAR(20) COMMENT '登录方式',
  141. ip_address VARCHAR(45) COMMENT 'IP地址',
  142. user_agent TEXT COMMENT '用户代理',
  143. success BOOLEAN DEFAULT FALSE COMMENT '是否成功',
  144. failure_reason VARCHAR(200) COMMENT '失败原因',
  145. login_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '登录时间',
  146. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  147. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  148. is_deleted BOOLEAN DEFAULT FALSE COMMENT '是否删除',
  149. INDEX idx_user_id (user_id),
  150. INDEX idx_username (username),
  151. INDEX idx_login_at (login_at),
  152. INDEX idx_ip_address (ip_address)
  153. ) COMMENT='登录日志表'
  154. """)
  155. connection.commit()
  156. print("✅ 数据库表创建成功")
  157. def create_default_data(connection):
  158. """创建默认数据"""
  159. cursor = connection.cursor()
  160. # 检查是否已有管理员用户
  161. cursor.execute("SELECT COUNT(*) FROM users WHERE username = 'admin'")
  162. if cursor.fetchone()[0] > 0:
  163. print("⚠️ 管理员用户已存在,跳过创建")
  164. # 获取现有的测试应用信息
  165. cursor.execute("SELECT app_key, app_secret FROM apps WHERE name = '测试应用' LIMIT 1")
  166. result = cursor.fetchone()
  167. if result:
  168. return result[0], result[1]
  169. else:
  170. # 如果没有测试应用,创建一个
  171. app_key = generate_random_string(32)
  172. app_secret = generate_random_string(64)
  173. return app_key, app_secret
  174. # 创建管理员用户
  175. admin_id = str(uuid.uuid4())
  176. admin_password = "Admin123456"
  177. password_hash = hash_password_simple(admin_password)
  178. cursor.execute("""
  179. INSERT INTO users (id, username, email, password_hash, is_active, is_superuser, created_at, updated_at, is_deleted)
  180. VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
  181. """, (admin_id, "admin", "admin@example.com", password_hash, True, True, datetime.now(), datetime.now(), False))
  182. # 创建测试应用
  183. app_id = str(uuid.uuid4())
  184. app_key = generate_random_string(32)
  185. app_secret = generate_random_string(64)
  186. cursor.execute("""
  187. INSERT INTO apps (id, name, app_key, app_secret, description, redirect_uris, scope, is_active, is_trusted,
  188. access_token_expires, refresh_token_expires, created_by, created_at, updated_at, is_deleted)
  189. VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
  190. """, (
  191. app_id, "测试应用", app_key, app_secret, "用于测试的默认应用",
  192. '["http://localhost:3000/callback", "http://localhost:8080/callback", "http://localhost:8001/auth/callback"]',
  193. '["profile", "email"]', True, True, 7200, 2592000, admin_id,
  194. datetime.now(), datetime.now(), False
  195. ))
  196. connection.commit()
  197. print("✅ 默认数据创建成功")
  198. print(f"管理员账号: admin")
  199. print(f"管理员密码: {admin_password}")
  200. print(f"测试应用Key: {app_key}")
  201. print(f"测试应用Secret: {app_secret}")
  202. return app_key, app_secret
  203. def main():
  204. """主函数"""
  205. print("=" * 50)
  206. print("SSO数据库初始化")
  207. print("=" * 50)
  208. try:
  209. # 获取数据库配置
  210. config = get_db_config()
  211. print(f"连接数据库: {config['host']}:{config['port']}/{config['database']}")
  212. # 连接数据库
  213. connection = pymysql.connect(**config)
  214. print("✅ 数据库连接成功")
  215. # 创建表
  216. create_tables(connection)
  217. # 创建默认数据
  218. result = create_default_data(connection)
  219. if result:
  220. app_key, app_secret = result
  221. else:
  222. app_key, app_secret = "demo_key", "demo_secret"
  223. # 关闭连接
  224. connection.close()
  225. print("\n" + "=" * 50)
  226. print("🎉 数据库初始化完成!")
  227. print("\n下一步:")
  228. print("1. 启动后端服务: cd src && python -m app.main")
  229. print("2. 启动前端服务: cd ../sso-frontend && npm run dev")
  230. print(f"3. 配置子系统: CLIENT_ID={app_key}")
  231. print(f" CLIENT_SECRET={app_secret}")
  232. except Exception as e:
  233. print(f"❌ 数据库初始化失败: {e}")
  234. sys.exit(1)
  235. if __name__ == "__main__":
  236. main()