1panel
This commit is contained in:
8
migrations/add_conversation_id.sql
Normal file
8
migrations/add_conversation_id.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- 添加 conversation_id 字段以支持對話持續性
|
||||
-- 這個字段用於在同一個翻譯任務中保持 Dify API 對話的連續性
|
||||
|
||||
ALTER TABLE dt_translation_jobs
|
||||
ADD COLUMN conversation_id VARCHAR(100) COMMENT 'Dify對話ID,用於維持翻譯上下文';
|
||||
|
||||
-- 為現有的 conversation_id 字段創建索引,以提高查詢效率
|
||||
CREATE INDEX idx_conversation_id ON dt_translation_jobs(conversation_id);
|
83
migrations/add_sys_user.sql
Normal file
83
migrations/add_sys_user.sql
Normal file
@@ -0,0 +1,83 @@
|
||||
-- 建立系統使用者表 (sys_user)
|
||||
-- 專門用於記錄帳號密碼和登入相關資訊
|
||||
-- 不影響現有 users 表的權限管理功能
|
||||
-- Created: 2025-10-01
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sys_user (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
-- 帳號資訊
|
||||
username VARCHAR(255) NOT NULL COMMENT '登入帳號',
|
||||
password_hash VARCHAR(512) COMMENT '密碼雜湊 (如果需要本地儲存)',
|
||||
email VARCHAR(255) NOT NULL COMMENT '電子郵件',
|
||||
display_name VARCHAR(255) COMMENT '顯示名稱',
|
||||
|
||||
-- API 認證資訊
|
||||
api_user_id VARCHAR(255) COMMENT 'API 回傳的使用者 ID',
|
||||
api_access_token TEXT COMMENT 'API 回傳的 access_token',
|
||||
api_token_expires_at TIMESTAMP NULL COMMENT 'API Token 過期時間',
|
||||
|
||||
-- 登入相關
|
||||
auth_method ENUM('API', 'LDAP') DEFAULT 'API' COMMENT '認證方式',
|
||||
last_login_at TIMESTAMP NULL COMMENT '最後登入時間',
|
||||
last_login_ip VARCHAR(45) COMMENT '最後登入 IP',
|
||||
login_count INT DEFAULT 0 COMMENT '登入次數',
|
||||
login_success_count INT DEFAULT 0 COMMENT '成功登入次數',
|
||||
login_fail_count INT DEFAULT 0 COMMENT '失敗登入次數',
|
||||
|
||||
-- 帳號狀態
|
||||
is_active BOOLEAN DEFAULT TRUE COMMENT '是否啟用',
|
||||
is_locked BOOLEAN DEFAULT FALSE COMMENT '是否鎖定',
|
||||
locked_until TIMESTAMP NULL COMMENT '鎖定至何時',
|
||||
|
||||
-- 審計欄位
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
|
||||
|
||||
-- 索引
|
||||
INDEX idx_username (username),
|
||||
INDEX idx_email (email),
|
||||
INDEX idx_api_user_id (api_user_id),
|
||||
INDEX idx_auth_method (auth_method),
|
||||
INDEX idx_last_login (last_login_at),
|
||||
INDEX idx_active_users (is_active, is_locked),
|
||||
|
||||
-- 約束
|
||||
UNIQUE KEY uk_username (username),
|
||||
UNIQUE KEY uk_email (email)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系統使用者表 - 帳號密碼登入記錄';
|
||||
|
||||
-- 建立登入記錄表 (簡化版)
|
||||
CREATE TABLE IF NOT EXISTS login_logs (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
-- 基本資訊
|
||||
username VARCHAR(255) NOT NULL COMMENT '登入帳號',
|
||||
auth_method ENUM('API', 'LDAP') NOT NULL COMMENT '認證方式',
|
||||
|
||||
-- 登入結果
|
||||
login_success BOOLEAN NOT NULL COMMENT '是否成功',
|
||||
error_message TEXT COMMENT '錯誤訊息(失敗時)',
|
||||
|
||||
-- 環境資訊
|
||||
ip_address VARCHAR(45) COMMENT 'IP 地址',
|
||||
user_agent TEXT COMMENT '瀏覽器資訊',
|
||||
|
||||
-- API 回應 (可選,用於除錯)
|
||||
api_response_summary JSON COMMENT 'API 回應摘要',
|
||||
|
||||
-- 時間
|
||||
login_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '登入時間',
|
||||
|
||||
-- 索引
|
||||
INDEX idx_username (username),
|
||||
INDEX idx_auth_method (auth_method),
|
||||
INDEX idx_login_success (login_success),
|
||||
INDEX idx_login_at (login_at),
|
||||
INDEX idx_username_time (username, login_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登入記錄表';
|
||||
|
||||
-- 清理舊的認證相關表(如果存在)
|
||||
DROP TABLE IF EXISTS auth_records;
|
||||
DROP TABLE IF EXISTS token_refresh_logs;
|
||||
DROP TABLE IF EXISTS login_history;
|
23
migrations/clean_dt_tables.sql
Normal file
23
migrations/clean_dt_tables.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
-- 清理所有 dt_ 前綴的資料表
|
||||
-- 重新開始,建立乾淨的架構
|
||||
-- Created: 2025-10-01
|
||||
|
||||
-- 關閉外鍵約束檢查 (避免刪除順序問題)
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- 刪除所有 dt_ 前綴的資料表 (按照依賴關係順序)
|
||||
-- 先刪除有外鍵依賴的子表,再刪除父表
|
||||
DROP TABLE IF EXISTS dt_job_files;
|
||||
DROP TABLE IF EXISTS dt_translation_cache;
|
||||
DROP TABLE IF EXISTS dt_api_usage_stats;
|
||||
DROP TABLE IF EXISTS dt_system_logs;
|
||||
DROP TABLE IF EXISTS dt_notifications;
|
||||
DROP TABLE IF EXISTS dt_login_logs;
|
||||
DROP TABLE IF EXISTS dt_translation_jobs;
|
||||
DROP TABLE IF EXISTS dt_users;
|
||||
|
||||
-- 重新啟用外鍵約束檢查
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- 驗證清理結果
|
||||
SHOW TABLES LIKE 'dt_%';
|
160
migrations/create_fresh_schema.sql
Normal file
160
migrations/create_fresh_schema.sql
Normal file
@@ -0,0 +1,160 @@
|
||||
-- 全新的文件翻譯系統資料庫架構
|
||||
-- 方案 A: dt_users 用於業務功能,sys_user 用於登入記錄
|
||||
-- API name 格式: 姓名+email,email 作為主要識別鍵
|
||||
-- Created: 2025-10-01
|
||||
|
||||
-- 1. 建立 dt_users 表 (業務功能使用)
|
||||
CREATE TABLE dt_users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(255) NOT NULL COMMENT 'API name (姓名+email格式)',
|
||||
display_name VARCHAR(255) NOT NULL COMMENT 'API name (姓名+email格式)',
|
||||
email VARCHAR(255) NOT NULL UNIQUE COMMENT '電子郵件 (主要識別鍵)',
|
||||
department VARCHAR(100) COMMENT '部門/職位',
|
||||
is_admin BOOLEAN DEFAULT FALSE COMMENT '是否為管理員',
|
||||
last_login DATETIME COMMENT '最後登入時間',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
|
||||
|
||||
INDEX idx_email (email),
|
||||
INDEX idx_username_email (username, email)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='使用者資訊表';
|
||||
|
||||
-- 2. 建立 dt_translation_jobs 表 (翻譯工作)
|
||||
CREATE TABLE dt_translation_jobs (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL COMMENT '關聯到 dt_users.id',
|
||||
job_name VARCHAR(255) NOT NULL COMMENT '工作名稱',
|
||||
source_lang VARCHAR(10) NOT NULL COMMENT '來源語言',
|
||||
target_lang VARCHAR(10) NOT NULL COMMENT '目標語言',
|
||||
file_type ENUM('DOCX', 'PPTX', 'PDF', 'TXT') NOT NULL COMMENT '檔案類型',
|
||||
status ENUM('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED') DEFAULT 'PENDING' COMMENT '工作狀態',
|
||||
progress INT DEFAULT 0 COMMENT '進度百分比',
|
||||
total_pages INT DEFAULT 0 COMMENT '總頁數',
|
||||
processed_pages INT DEFAULT 0 COMMENT '已處理頁數',
|
||||
cost DECIMAL(10,4) DEFAULT 0 COMMENT '翻譯成本',
|
||||
error_message TEXT COMMENT '錯誤訊息',
|
||||
conversation_id VARCHAR(255) COMMENT 'Dify 對話 ID',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
|
||||
completed_at DATETIME COMMENT '完成時間',
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES dt_users(id) ON DELETE CASCADE,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='翻譯工作表';
|
||||
|
||||
-- 3. 建立 dt_job_files 表 (工作檔案)
|
||||
CREATE TABLE dt_job_files (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
job_id BIGINT NOT NULL COMMENT '關聯到 dt_translation_jobs.id',
|
||||
file_type ENUM('source', 'translated') NOT NULL COMMENT '檔案類型',
|
||||
original_filename VARCHAR(255) NOT NULL COMMENT '原始檔名',
|
||||
stored_filename VARCHAR(255) NOT NULL COMMENT '儲存檔名',
|
||||
file_path VARCHAR(500) NOT NULL COMMENT '檔案路徑',
|
||||
file_size BIGINT DEFAULT 0 COMMENT '檔案大小',
|
||||
mime_type VARCHAR(100) COMMENT 'MIME 類型',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
|
||||
|
||||
FOREIGN KEY (job_id) REFERENCES dt_translation_jobs(id) ON DELETE CASCADE,
|
||||
INDEX idx_job_id (job_id),
|
||||
INDEX idx_file_type (file_type)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作檔案表';
|
||||
|
||||
-- 4. 建立 dt_translation_cache 表 (翻譯快取)
|
||||
CREATE TABLE dt_translation_cache (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
content_hash VARCHAR(64) NOT NULL COMMENT '內容雜湊',
|
||||
source_lang VARCHAR(10) NOT NULL COMMENT '來源語言',
|
||||
target_lang VARCHAR(10) NOT NULL COMMENT '目標語言',
|
||||
source_text TEXT NOT NULL COMMENT '來源文字',
|
||||
translated_text TEXT NOT NULL COMMENT '翻譯文字',
|
||||
quality_score DECIMAL(3,2) DEFAULT 0.00 COMMENT '品質分數',
|
||||
hit_count INT DEFAULT 0 COMMENT '命中次數',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
|
||||
last_used_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最後使用時間',
|
||||
|
||||
UNIQUE KEY uk_content_lang (content_hash, source_lang, target_lang),
|
||||
INDEX idx_last_used (last_used_at),
|
||||
INDEX idx_hit_count (hit_count)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='翻譯快取表';
|
||||
|
||||
-- 5. 建立 dt_api_usage_stats 表 (API 使用統計)
|
||||
CREATE TABLE dt_api_usage_stats (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL COMMENT '關聯到 dt_users.id',
|
||||
job_id BIGINT COMMENT '關聯到 dt_translation_jobs.id',
|
||||
api_name VARCHAR(50) NOT NULL COMMENT 'API 名稱',
|
||||
request_count INT DEFAULT 1 COMMENT '請求次數',
|
||||
token_used INT DEFAULT 0 COMMENT '使用的 token 數',
|
||||
cost DECIMAL(10,4) DEFAULT 0 COMMENT '成本',
|
||||
response_time_ms INT DEFAULT 0 COMMENT '回應時間(毫秒)',
|
||||
status ENUM('SUCCESS', 'FAILED', 'TIMEOUT') DEFAULT 'SUCCESS' COMMENT '狀態',
|
||||
error_message TEXT COMMENT '錯誤訊息',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
|
||||
date_key DATE GENERATED ALWAYS AS (DATE(created_at)) STORED COMMENT '日期鍵',
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES dt_users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (job_id) REFERENCES dt_translation_jobs(id) ON DELETE SET NULL,
|
||||
INDEX idx_user_date (user_id, date_key),
|
||||
INDEX idx_api_name (api_name),
|
||||
INDEX idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='API 使用統計表';
|
||||
|
||||
-- 6. 建立 dt_system_logs 表 (系統日誌)
|
||||
CREATE TABLE dt_system_logs (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
level ENUM('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL') NOT NULL COMMENT '日誌級別',
|
||||
category VARCHAR(50) NOT NULL COMMENT '日誌分類',
|
||||
message TEXT NOT NULL COMMENT '日誌訊息',
|
||||
user_id INT COMMENT '關聯到 dt_users.id',
|
||||
job_id BIGINT COMMENT '關聯到 dt_translation_jobs.id',
|
||||
extra_data JSON COMMENT '額外資料',
|
||||
ip_address VARCHAR(45) COMMENT 'IP 地址',
|
||||
user_agent TEXT COMMENT '用戶代理',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
|
||||
date_key DATE GENERATED ALWAYS AS (DATE(created_at)) STORED COMMENT '日期鍵',
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES dt_users(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (job_id) REFERENCES dt_translation_jobs(id) ON DELETE SET NULL,
|
||||
INDEX idx_level_category (level, category),
|
||||
INDEX idx_user_date (user_id, date_key),
|
||||
INDEX idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系統日誌表';
|
||||
|
||||
-- 7. 建立 dt_notifications 表 (通知)
|
||||
CREATE TABLE dt_notifications (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL COMMENT '關聯到 dt_users.id',
|
||||
type ENUM('INFO', 'SUCCESS', 'WARNING', 'ERROR') NOT NULL COMMENT '通知類型',
|
||||
title VARCHAR(255) NOT NULL COMMENT '通知標題',
|
||||
message TEXT NOT NULL COMMENT '通知內容',
|
||||
is_read BOOLEAN DEFAULT FALSE COMMENT '是否已讀',
|
||||
data JSON COMMENT '額外資料',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
|
||||
read_at DATETIME COMMENT '已讀時間',
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES dt_users(id) ON DELETE CASCADE,
|
||||
INDEX idx_user_unread (user_id, is_read),
|
||||
INDEX idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知表';
|
||||
|
||||
-- 8. 保持現有的 sys_user 表 (專門用於登入記錄)
|
||||
-- sys_user 表已存在,透過 email 與 dt_users 關聯
|
||||
|
||||
-- 9. 重新命名 login_logs 為 dt_login_logs
|
||||
RENAME TABLE login_logs TO dt_login_logs;
|
||||
|
||||
-- 10. 為 dt_login_logs 添加與 dt_users 的關聯
|
||||
ALTER TABLE dt_login_logs
|
||||
ADD COLUMN user_id INT COMMENT '關聯到 dt_users.id',
|
||||
ADD INDEX idx_user_id (user_id),
|
||||
ADD FOREIGN KEY fk_dt_login_logs_user_id (user_id) REFERENCES dt_users(id) ON DELETE SET NULL;
|
||||
|
||||
-- 11. 插入預設管理員使用者
|
||||
INSERT INTO dt_users (username, display_name, email, department, is_admin)
|
||||
VALUES ('ymirliu ymirliu@panjit.com.tw', 'ymirliu ymirliu@panjit.com.tw', 'ymirliu@panjit.com.tw', 'IT', TRUE);
|
||||
|
||||
-- 12. 驗證架構建立
|
||||
SELECT 'Tables created:' as status;
|
||||
SHOW TABLES LIKE 'dt_%';
|
203
migrations/fix_api_usage_stats.py
Normal file
203
migrations/fix_api_usage_stats.py
Normal file
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
執行 API Usage Stats 資料表修復 Migration
|
||||
|
||||
Usage:
|
||||
python migrations/fix_api_usage_stats.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加專案根目錄到 Python 路徑
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from app import create_app, db
|
||||
from sqlalchemy import text
|
||||
|
||||
|
||||
def run_migration():
|
||||
"""執行資料表結構修復"""
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
print("=" * 60)
|
||||
print("API Usage Stats 資料表結構修復")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 1. 檢查當前結構
|
||||
print("\n[1/8] 檢查當前資料表結構...")
|
||||
result = db.session.execute(text('SHOW COLUMNS FROM dt_api_usage_stats'))
|
||||
current_columns = {row[0]: row for row in result}
|
||||
print(f" ✓ 當前欄位數量: {len(current_columns)}")
|
||||
|
||||
# 2. 備份現有資料
|
||||
print("\n[2/8] 建立資料備份...")
|
||||
db.session.execute(text('''
|
||||
CREATE TABLE IF NOT EXISTS dt_api_usage_stats_backup_20251001
|
||||
AS SELECT * FROM dt_api_usage_stats
|
||||
'''))
|
||||
db.session.commit()
|
||||
|
||||
backup_count = db.session.execute(
|
||||
text('SELECT COUNT(*) FROM dt_api_usage_stats_backup_20251001')
|
||||
).scalar()
|
||||
print(f" ✓ 已備份 {backup_count} 筆記錄")
|
||||
|
||||
# 3. 修改欄位名稱:api_name → api_endpoint
|
||||
if 'api_name' in current_columns:
|
||||
print("\n[3/8] 重新命名 api_name → api_endpoint...")
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_api_usage_stats
|
||||
CHANGE COLUMN api_name api_endpoint VARCHAR(200) NOT NULL COMMENT 'API端點'
|
||||
'''))
|
||||
db.session.commit()
|
||||
print(" ✓ 已重新命名 api_name → api_endpoint")
|
||||
else:
|
||||
print("\n[3/8] 跳過(api_name 已不存在或已是 api_endpoint)")
|
||||
|
||||
# 4. 新增 prompt_tokens 和 completion_tokens
|
||||
print("\n[4/8] 新增 prompt_tokens 和 completion_tokens...")
|
||||
if 'prompt_tokens' not in current_columns:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_api_usage_stats
|
||||
ADD COLUMN prompt_tokens INT DEFAULT 0 COMMENT 'Prompt token數' AFTER api_endpoint
|
||||
'''))
|
||||
if 'completion_tokens' not in current_columns:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_api_usage_stats
|
||||
ADD COLUMN completion_tokens INT DEFAULT 0 COMMENT 'Completion token數' AFTER prompt_tokens
|
||||
'''))
|
||||
db.session.commit()
|
||||
print(" ✓ 已新增 token 細分欄位")
|
||||
|
||||
# 5. 重新命名 token_used → total_tokens
|
||||
if 'token_used' in current_columns:
|
||||
print("\n[5/8] 重新命名 token_used → total_tokens...")
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_api_usage_stats
|
||||
CHANGE COLUMN token_used total_tokens INT DEFAULT 0 COMMENT '總token數'
|
||||
'''))
|
||||
db.session.commit()
|
||||
print(" ✓ 已重新命名 token_used → total_tokens")
|
||||
else:
|
||||
print("\n[5/8] 跳過(token_used 已不存在或已是 total_tokens)")
|
||||
|
||||
# 6. 新增計費相關欄位
|
||||
print("\n[6/8] 新增計費相關欄位...")
|
||||
if 'prompt_unit_price' not in current_columns:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_api_usage_stats
|
||||
ADD COLUMN prompt_unit_price DECIMAL(10, 8) DEFAULT 0.00000000 COMMENT '單價' AFTER total_tokens
|
||||
'''))
|
||||
if 'prompt_price_unit' not in current_columns:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_api_usage_stats
|
||||
ADD COLUMN prompt_price_unit VARCHAR(20) DEFAULT 'USD' COMMENT '價格單位' AFTER prompt_unit_price
|
||||
'''))
|
||||
db.session.commit()
|
||||
print(" ✓ 已新增計費欄位")
|
||||
|
||||
# 7. 替換 status 欄位為 success (BOOLEAN)
|
||||
print("\n[7/8] 更新 status 欄位...")
|
||||
if 'status' in current_columns and 'success' not in current_columns:
|
||||
# 先新增 success 欄位
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_api_usage_stats
|
||||
ADD COLUMN success BOOLEAN DEFAULT TRUE COMMENT '是否成功' AFTER response_time_ms
|
||||
'''))
|
||||
|
||||
# 將 status 資料轉換到 success
|
||||
db.session.execute(text('''
|
||||
UPDATE dt_api_usage_stats
|
||||
SET success = (status = 'SUCCESS')
|
||||
'''))
|
||||
|
||||
# 刪除舊的 status 欄位
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_api_usage_stats
|
||||
DROP COLUMN status
|
||||
'''))
|
||||
db.session.commit()
|
||||
print(" ✓ 已將 status 轉換為 success (BOOLEAN)")
|
||||
else:
|
||||
print(" ℹ 跳過(已完成或不需要轉換)")
|
||||
|
||||
# 8. 更新索引
|
||||
print("\n[8/8] 建立索引...")
|
||||
try:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_api_usage_stats
|
||||
ADD INDEX IF NOT EXISTS idx_api_endpoint (api_endpoint)
|
||||
'''))
|
||||
except Exception as e:
|
||||
if 'Duplicate' not in str(e):
|
||||
raise
|
||||
|
||||
try:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_api_usage_stats
|
||||
ADD INDEX IF NOT EXISTS idx_success (success)
|
||||
'''))
|
||||
except Exception as e:
|
||||
if 'Duplicate' not in str(e):
|
||||
raise
|
||||
|
||||
db.session.commit()
|
||||
print(" ✓ 已建立索引")
|
||||
|
||||
# 9. 驗證最終結構
|
||||
print("\n" + "=" * 60)
|
||||
print("驗證最終資料表結構")
|
||||
print("=" * 60)
|
||||
|
||||
result = db.session.execute(text('SHOW COLUMNS FROM dt_api_usage_stats'))
|
||||
final_columns = list(result)
|
||||
|
||||
print(f"\n最終欄位列表 (共 {len(final_columns)} 個):")
|
||||
for col in final_columns:
|
||||
print(f" - {col[0]:25} {col[1]:20} NULL={col[2]} Default={col[4]}")
|
||||
|
||||
# 10. 統計資料
|
||||
print("\n" + "=" * 60)
|
||||
print("資料統計")
|
||||
print("=" * 60)
|
||||
|
||||
total_records = db.session.execute(
|
||||
text('SELECT COUNT(*) FROM dt_api_usage_stats')
|
||||
).scalar()
|
||||
print(f"總記錄數: {total_records}")
|
||||
|
||||
if total_records > 0:
|
||||
stats = db.session.execute(text('''
|
||||
SELECT
|
||||
api_endpoint,
|
||||
COUNT(*) as count,
|
||||
SUM(total_tokens) as total_tokens,
|
||||
SUM(cost) as total_cost
|
||||
FROM dt_api_usage_stats
|
||||
GROUP BY api_endpoint
|
||||
''')).fetchall()
|
||||
|
||||
print("\nAPI 使用統計:")
|
||||
for stat in stats:
|
||||
print(f" {stat[0]:40} | {stat[1]:5} 次 | {stat[2]:10} tokens | ${stat[3]:.4f}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ Migration 執行完成!")
|
||||
print("=" * 60)
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"\n❌ Migration 失敗: {str(e)}")
|
||||
print("\n可以使用備份表還原資料:")
|
||||
print(" DROP TABLE dt_api_usage_stats;")
|
||||
print(" CREATE TABLE dt_api_usage_stats AS SELECT * FROM dt_api_usage_stats_backup_20251001;")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_migration()
|
36
migrations/fix_auth_architecture.sql
Normal file
36
migrations/fix_auth_architecture.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
-- 修正認證系統架構
|
||||
-- 方案 A: 保留 dt_users 的 username 和 display_name,都使用 API 回傳的 name (姓名+email)
|
||||
-- 使用 email 作為主要唯一識別碼,sys_user 表專門記錄登入資訊
|
||||
-- Created: 2025-10-01
|
||||
|
||||
-- 1. 確保 dt_users 表的 email 唯一約束
|
||||
-- 先檢查是否有重複的 email,如果有則需要手動處理
|
||||
-- 因為有外鍵約束,不能直接刪除
|
||||
-- 先顯示重複的 email 記錄讓管理員確認
|
||||
-- SELECT email, COUNT(*) as count FROM dt_users GROUP BY email HAVING COUNT(*) > 1;
|
||||
|
||||
-- 添加 email 唯一約束
|
||||
ALTER TABLE dt_users
|
||||
ADD CONSTRAINT uk_dt_users_email UNIQUE (email);
|
||||
|
||||
-- 2. 調整現有欄位註解,說明新的使用方式
|
||||
ALTER TABLE dt_users
|
||||
MODIFY COLUMN username VARCHAR(255) NOT NULL COMMENT 'API name (姓名+email格式)',
|
||||
MODIFY COLUMN email VARCHAR(255) NOT NULL COMMENT '電子郵件 (主要識別鍵)';
|
||||
|
||||
-- 3. 保持 sys_user 表結構,但調整為專門記錄登入資訊
|
||||
-- sys_user 表通過 email 與 dt_users 關聯
|
||||
-- (保留現有的 sys_user 表,因為它是專門用於登入記錄)
|
||||
|
||||
-- 4. 重新命名 login_logs 為 dt_login_logs (配合專案命名規則)
|
||||
RENAME TABLE login_logs TO dt_login_logs;
|
||||
|
||||
-- 5. 更新 dt_login_logs 表結構 (配合 dt_users 的主鍵)
|
||||
ALTER TABLE dt_login_logs
|
||||
ADD COLUMN user_id INT COMMENT '關聯到 dt_users.id',
|
||||
ADD INDEX idx_user_id (user_id),
|
||||
ADD FOREIGN KEY fk_dt_login_logs_user_id (user_id) REFERENCES dt_users(id) ON DELETE SET NULL;
|
||||
|
||||
-- 6. 建立使用者識別索引 (支援 email 和 username 快速查詢)
|
||||
ALTER TABLE dt_users
|
||||
ADD INDEX idx_username_email (username, email);
|
193
migrations/fix_translation_cache.py
Normal file
193
migrations/fix_translation_cache.py
Normal file
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
修復 dt_translation_cache 資料表結構
|
||||
|
||||
問題:資料表欄位名稱與模型定義不一致
|
||||
- content_hash → source_text_hash
|
||||
- source_lang → source_language
|
||||
- target_lang → target_language
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from app import create_app, db
|
||||
from sqlalchemy import text
|
||||
|
||||
|
||||
def run_migration():
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
print("=" * 60)
|
||||
print("修復 dt_translation_cache 資料表結構")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 1. 檢查當前結構
|
||||
print("\n[1/6] 檢查當前資料表結構...")
|
||||
result = db.session.execute(text('SHOW COLUMNS FROM dt_translation_cache'))
|
||||
current_columns = {row[0]: row for row in result}
|
||||
print(f" ✓ 當前欄位: {', '.join(current_columns.keys())}")
|
||||
|
||||
# 2. 備份資料
|
||||
print("\n[2/6] 備份現有資料...")
|
||||
db.session.execute(text('''
|
||||
CREATE TABLE IF NOT EXISTS dt_translation_cache_backup_20251001
|
||||
AS SELECT * FROM dt_translation_cache
|
||||
'''))
|
||||
db.session.commit()
|
||||
|
||||
backup_count = db.session.execute(
|
||||
text('SELECT COUNT(*) FROM dt_translation_cache_backup_20251001')
|
||||
).scalar()
|
||||
print(f" ✓ 已備份 {backup_count} 筆記錄")
|
||||
|
||||
# 3. 重新命名欄位:content_hash → source_text_hash
|
||||
if 'content_hash' in current_columns and 'source_text_hash' not in current_columns:
|
||||
print("\n[3/6] 重新命名 content_hash → source_text_hash...")
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_translation_cache
|
||||
CHANGE COLUMN content_hash source_text_hash VARCHAR(64) NOT NULL COMMENT '來源文字hash'
|
||||
'''))
|
||||
db.session.commit()
|
||||
print(" ✓ 已重新命名")
|
||||
else:
|
||||
print("\n[3/6] 跳過(已經是 source_text_hash)")
|
||||
|
||||
# 4. 重新命名欄位:source_lang → source_language
|
||||
if 'source_lang' in current_columns and 'source_language' not in current_columns:
|
||||
print("\n[4/6] 重新命名 source_lang → source_language...")
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_translation_cache
|
||||
CHANGE COLUMN source_lang source_language VARCHAR(50) NOT NULL COMMENT '來源語言'
|
||||
'''))
|
||||
db.session.commit()
|
||||
print(" ✓ 已重新命名")
|
||||
else:
|
||||
print("\n[4/6] 跳過(已經是 source_language)")
|
||||
|
||||
# 5. 重新命名欄位:target_lang → target_language
|
||||
if 'target_lang' in current_columns and 'target_language' not in current_columns:
|
||||
print("\n[5/6] 重新命名 target_lang → target_language...")
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_translation_cache
|
||||
CHANGE COLUMN target_lang target_language VARCHAR(50) NOT NULL COMMENT '目標語言'
|
||||
'''))
|
||||
db.session.commit()
|
||||
print(" ✓ 已重新命名")
|
||||
else:
|
||||
print("\n[5/6] 跳過(已經是 target_language)")
|
||||
|
||||
# 6. 刪除不需要的欄位
|
||||
print("\n[6/6] 清理多餘欄位...")
|
||||
|
||||
# 檢查並刪除 quality_score
|
||||
if 'quality_score' in current_columns:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_translation_cache
|
||||
DROP COLUMN quality_score
|
||||
'''))
|
||||
print(" ✓ 已刪除 quality_score")
|
||||
|
||||
# 檢查並刪除 hit_count
|
||||
if 'hit_count' in current_columns:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_translation_cache
|
||||
DROP COLUMN hit_count
|
||||
'''))
|
||||
print(" ✓ 已刪除 hit_count")
|
||||
|
||||
# 檢查並刪除 last_used_at
|
||||
if 'last_used_at' in current_columns:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_translation_cache
|
||||
DROP COLUMN last_used_at
|
||||
'''))
|
||||
print(" ✓ 已刪除 last_used_at")
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# 7. 重建索引和約束
|
||||
print("\n[7/7] 重建索引和約束...")
|
||||
|
||||
# 先刪除舊的唯一約束(如果存在)
|
||||
try:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_translation_cache
|
||||
DROP INDEX idx_content_hash
|
||||
'''))
|
||||
print(" ✓ 已刪除舊索引 idx_content_hash")
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_translation_cache
|
||||
DROP INDEX idx_source_lang_target_lang
|
||||
'''))
|
||||
print(" ✓ 已刪除舊索引 idx_source_lang_target_lang")
|
||||
except:
|
||||
pass
|
||||
|
||||
# 建立新的唯一約束
|
||||
try:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_translation_cache
|
||||
ADD UNIQUE KEY uk_cache (source_text_hash, source_language, target_language)
|
||||
'''))
|
||||
print(" ✓ 已建立唯一約束 uk_cache")
|
||||
except Exception as e:
|
||||
if 'Duplicate' not in str(e):
|
||||
print(f" ⚠ 約束建立警告: {str(e)}")
|
||||
|
||||
# 建立語言索引
|
||||
try:
|
||||
db.session.execute(text('''
|
||||
ALTER TABLE dt_translation_cache
|
||||
ADD INDEX idx_languages (source_language, target_language)
|
||||
'''))
|
||||
print(" ✓ 已建立索引 idx_languages")
|
||||
except Exception as e:
|
||||
if 'Duplicate' not in str(e):
|
||||
print(f" ⚠ 索引建立警告: {str(e)}")
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# 驗證最終結構
|
||||
print("\n" + "=" * 60)
|
||||
print("驗證最終資料表結構")
|
||||
print("=" * 60)
|
||||
|
||||
result = db.session.execute(text('SHOW COLUMNS FROM dt_translation_cache'))
|
||||
final_columns = list(result)
|
||||
|
||||
print(f"\n最終欄位列表 (共 {len(final_columns)} 個):")
|
||||
for col in final_columns:
|
||||
print(f" - {col[0]:30} {col[1]:30} NULL={col[2]}")
|
||||
|
||||
# 顯示索引
|
||||
print("\n索引:")
|
||||
result = db.session.execute(text('SHOW INDEX FROM dt_translation_cache'))
|
||||
for idx in result:
|
||||
print(f" - {idx[2]:30} -> {idx[4]}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ Migration 執行完成!")
|
||||
print("=" * 60)
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"\n❌ Migration 失敗: {str(e)}")
|
||||
print("\n可以使用備份表還原資料:")
|
||||
print(" DROP TABLE dt_translation_cache;")
|
||||
print(" CREATE TABLE dt_translation_cache AS SELECT * FROM dt_translation_cache_backup_20251001;")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_migration()
|
19
migrations/merge_duplicate_users.sql
Normal file
19
migrations/merge_duplicate_users.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- 合併重複的使用者記錄
|
||||
-- 保留 ID=3 的記錄 (較新且有較多關聯資料)
|
||||
-- 將 ID=1 的關聯資料轉移到 ID=3,然後刪除 ID=1
|
||||
|
||||
-- 1. 將 ID=1 的 system_logs 轉移到 ID=3
|
||||
UPDATE dt_system_logs SET user_id = 3 WHERE user_id = 1;
|
||||
|
||||
-- 2. 確認沒有其他關聯資料需要轉移
|
||||
-- (dt_translation_jobs, dt_api_usage_stats 都已經在 ID=3)
|
||||
|
||||
-- 3. 刪除重複的記錄 ID=1
|
||||
DELETE FROM dt_users WHERE id = 1;
|
||||
|
||||
-- 4. 驗證結果
|
||||
SELECT 'After merge:' as status;
|
||||
SELECT id, username, display_name, email FROM dt_users WHERE email = 'ymirliu@panjit.com.tw';
|
||||
SELECT 'Jobs:', COUNT(*) FROM dt_translation_jobs WHERE user_id = 3;
|
||||
SELECT 'Logs:', COUNT(*) FROM dt_system_logs WHERE user_id = 3;
|
||||
SELECT 'Stats:', COUNT(*) FROM dt_api_usage_stats WHERE user_id = 3;
|
Reference in New Issue
Block a user