fix db bug
This commit is contained in:
parent
6d636e9799
commit
79368c11e2
@ -4,8 +4,16 @@ from dotenv import load_dotenv
|
|||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
# 数据库
|
# 数据库:优先从 DB_HOST 等环境变量拼接 MySQL URL,否则回退到 DATABASE_URL / SQLite
|
||||||
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./airlabs.db")
|
DB_HOST = os.getenv("DB_HOST", "")
|
||||||
|
if DB_HOST:
|
||||||
|
DB_USER = os.getenv("DB_USER", "airlabs_manage")
|
||||||
|
DB_PASSWORD = os.getenv("DB_PASSWORD", "")
|
||||||
|
DB_PORT = os.getenv("DB_PORT", "3306")
|
||||||
|
DB_NAME = os.getenv("DB_NAME", "airlabs_manage")
|
||||||
|
DATABASE_URL = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?charset=utf8mb4"
|
||||||
|
else:
|
||||||
|
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./airlabs.db")
|
||||||
|
|
||||||
# JWT 认证
|
# JWT 认证
|
||||||
SECRET_KEY = os.getenv("SECRET_KEY", "airlabs-project-secret-key-change-in-production")
|
SECRET_KEY = os.getenv("SECRET_KEY", "airlabs-project-secret-key-change-in-production")
|
||||||
|
|||||||
@ -3,7 +3,9 @@ from sqlalchemy import create_engine
|
|||||||
from sqlalchemy.orm import sessionmaker, declarative_base
|
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||||
from config import DATABASE_URL
|
from config import DATABASE_URL
|
||||||
|
|
||||||
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
# check_same_thread 仅 SQLite 需要
|
||||||
|
connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}
|
||||||
|
engine = create_engine(DATABASE_URL, connect_args=connect_args)
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,17 @@ logger = logging.getLogger(__name__)
|
|||||||
# 创建所有表
|
# 创建所有表
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
# ── 手动迁移:为旧 SQLite 数据库补充 role_id 列 ──
|
||||||
|
from config import DATABASE_URL as _db_url
|
||||||
|
if _db_url.startswith("sqlite"):
|
||||||
|
import sqlalchemy
|
||||||
|
with engine.connect() as conn:
|
||||||
|
cols = [r[1] for r in conn.execute(sqlalchemy.text("PRAGMA table_info('users')"))]
|
||||||
|
if "role_id" not in cols:
|
||||||
|
conn.execute(sqlalchemy.text("ALTER TABLE users ADD COLUMN role_id INTEGER"))
|
||||||
|
conn.commit()
|
||||||
|
logger.info("[MIGRATE] added column users.role_id")
|
||||||
|
|
||||||
app = FastAPI(title="AirLabs Project", version="1.0.0")
|
app = FastAPI(title="AirLabs Project", version="1.0.0")
|
||||||
|
|
||||||
# CORS
|
# CORS
|
||||||
@ -144,6 +155,16 @@ def init_roles_and_admin():
|
|||||||
print(f"[MIGRATE] added default milestones for project: {proj.name}")
|
print(f"[MIGRATE] added default milestones for project: {proj.name}")
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
# 迁移:为旧用户补充默认 role_id(成员角色)
|
||||||
|
member_role = db.query(Role).filter(Role.name == "成员").first()
|
||||||
|
if member_role:
|
||||||
|
orphans = db.query(User).filter(User.role_id.is_(None)).all()
|
||||||
|
for u in orphans:
|
||||||
|
u.role_id = member_role.id
|
||||||
|
print(f"[MIGRATE] assigned default role '成员' to user: {u.username}")
|
||||||
|
if orphans:
|
||||||
|
db.commit()
|
||||||
|
|
||||||
# 创建默认管理员(关联超级管理员角色)
|
# 创建默认管理员(关联超级管理员角色)
|
||||||
admin_role = db.query(Role).filter(Role.name == "超级管理员").first()
|
admin_role = db.query(Role).filter(Role.name == "超级管理员").first()
|
||||||
if admin_role and not db.query(User).filter(User.username == "admin").first():
|
if admin_role and not db.query(User).filter(User.username == "admin").first():
|
||||||
|
|||||||
77
backend/migrate_sqlite_to_mysql.py
Normal file
77
backend/migrate_sqlite_to_mysql.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"""一次性脚本:将 SQLite 数据迁移到 MySQL"""
|
||||||
|
import sqlite3
|
||||||
|
from sqlalchemy import create_engine, text
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from config import DATABASE_URL
|
||||||
|
|
||||||
|
SQLITE_PATH = "airlabs.db"
|
||||||
|
|
||||||
|
# 按外键依赖顺序排列
|
||||||
|
TABLES = [
|
||||||
|
"roles",
|
||||||
|
"users",
|
||||||
|
"projects",
|
||||||
|
"project_milestones",
|
||||||
|
"submissions",
|
||||||
|
"submission_history",
|
||||||
|
"ai_tool_costs",
|
||||||
|
"ai_tool_cost_allocations",
|
||||||
|
"outsource_costs",
|
||||||
|
"cost_overrides",
|
||||||
|
"overhead_costs",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def migrate():
|
||||||
|
if not DATABASE_URL.startswith("mysql"):
|
||||||
|
print("ERROR: DATABASE_URL 不是 MySQL,请检查 .env 配置")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 连接 SQLite
|
||||||
|
sqlite_conn = sqlite3.connect(SQLITE_PATH)
|
||||||
|
sqlite_conn.row_factory = sqlite3.Row
|
||||||
|
|
||||||
|
# 连接 MySQL — 先建表
|
||||||
|
from database import Base, engine
|
||||||
|
import models # noqa: F401 — 确保所有模型已注册
|
||||||
|
Base.metadata.create_all(bind=engine)
|
||||||
|
print("[OK] MySQL tables created")
|
||||||
|
|
||||||
|
mysql_engine = engine
|
||||||
|
Session = sessionmaker(bind=mysql_engine)
|
||||||
|
session = Session()
|
||||||
|
|
||||||
|
try:
|
||||||
|
for table in TABLES:
|
||||||
|
rows = sqlite_conn.execute(f"SELECT * FROM {table}").fetchall()
|
||||||
|
if not rows:
|
||||||
|
print(f" {table}: 0 rows (skip)")
|
||||||
|
continue
|
||||||
|
|
||||||
|
cols = rows[0].keys()
|
||||||
|
col_list = ", ".join(cols)
|
||||||
|
param_list = ", ".join(f":{c}" for c in cols)
|
||||||
|
insert_sql = text(f"INSERT INTO {table} ({col_list}) VALUES ({param_list})")
|
||||||
|
|
||||||
|
# 清空目标表(避免重复运行冲突)
|
||||||
|
session.execute(text(f"DELETE FROM {table}"))
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
data = dict(row)
|
||||||
|
session.execute(insert_sql, data)
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
print(f" {table}: {len(rows)} rows migrated")
|
||||||
|
|
||||||
|
print("\n[DONE] Migration complete!")
|
||||||
|
except Exception as e:
|
||||||
|
session.rollback()
|
||||||
|
print(f"\n[ERROR] Migration failed: {e}")
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
sqlite_conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
migrate()
|
||||||
@ -10,3 +10,4 @@ openai
|
|||||||
httpx
|
httpx
|
||||||
apscheduler
|
apscheduler
|
||||||
python-dotenv
|
python-dotenv
|
||||||
|
pymysql
|
||||||
|
|||||||
@ -1,15 +1,3 @@
|
|||||||
# SQLite 持久化存储
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: airlabs-manage-sqlite-data
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 1Gi
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@ -33,18 +21,23 @@ spec:
|
|||||||
ports:
|
ports:
|
||||||
- containerPort: 8000
|
- containerPort: 8000
|
||||||
env:
|
env:
|
||||||
# SQLite 存储在持久卷中
|
# Database (阿里云 RDS MySQL)
|
||||||
- name: DATABASE_URL
|
- name: DB_HOST
|
||||||
value: "sqlite:////app/data/airlabs.db"
|
value: "rm-7xv1uaw910558p1788o.mysql.rds.aliyuncs.com"
|
||||||
# 生产环境 JWT 密钥(部署前请修改)
|
- name: DB_NAME
|
||||||
|
value: "airlabs-manage"
|
||||||
|
- name: DB_USER
|
||||||
|
value: "airlabs_manage"
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
value: "Airlabs-manage123"
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "3306"
|
||||||
|
# 生产环境 JWT 密钥
|
||||||
- name: SECRET_KEY
|
- name: SECRET_KEY
|
||||||
value: "Ui5-xEvtAhKRDtlXKzDfd7TElsVZFUhakff0qcjn8jU"
|
value: "Ui5-xEvtAhKRDtlXKzDfd7TElsVZFUhakff0qcjn8jU"
|
||||||
# CORS 允许的域名
|
# CORS 允许的域名
|
||||||
- name: CORS_ORIGINS
|
- name: CORS_ORIGINS
|
||||||
value: "https://airlabs-manage-web.airlabs.art"
|
value: "https://airlabs-manage-web.airlabs.art"
|
||||||
volumeMounts:
|
|
||||||
- name: sqlite-data
|
|
||||||
mountPath: /app/data
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
memory: "128Mi"
|
memory: "128Mi"
|
||||||
@ -52,10 +45,6 @@ spec:
|
|||||||
limits:
|
limits:
|
||||||
memory: "512Mi"
|
memory: "512Mi"
|
||||||
cpu: "500m"
|
cpu: "500m"
|
||||||
volumes:
|
|
||||||
- name: sqlite-data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: airlabs-manage-sqlite-data
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user