優化授權時間計算邏輯統一使用台灣時區
Browse files- 後端授權時間計算改用台灣時間00:00作為起始點(-8hr)
- SQL範例資料同步調整時間計算邏輯
- 更新activity_logs視圖新增license_id欄位並優化創建方式
- 統一前後端時間處理確保一致性
- app/services/license_service.py +6 -7
- supabase-schema.sql +23 -19
app/services/license_service.py
CHANGED
|
@@ -37,11 +37,11 @@ class LicenseService:
|
|
| 37 |
|
| 38 |
license_code = self.generate_license_code()
|
| 39 |
|
| 40 |
-
# 計算到期時間:從當天00:00開始計算指定天數
|
| 41 |
if license_data.expires_days > 0:
|
| 42 |
-
# 取得今天的午夜(00:00:00)作為起始時間
|
| 43 |
today = datetime.now(timezone.utc).date()
|
| 44 |
-
start_date = datetime.combine(today, datetime.min.time(), timezone.utc)
|
| 45 |
# 加上指定天數,到期日為最後一天的23:59:59
|
| 46 |
expires_at = start_date + timedelta(days=license_data.expires_days) - timedelta(seconds=1)
|
| 47 |
else:
|
|
@@ -422,18 +422,17 @@ class LicenseService:
|
|
| 422 |
license_data = result.data[0]
|
| 423 |
current_expires_at = datetime.fromisoformat(license_data["expires_at"].replace('Z', '+00:00'))
|
| 424 |
|
| 425 |
-
# 計算新的到期時間:從原到期日開始加天數
|
| 426 |
-
# 確保新的到期時間也是以天為界限(23:59:59)
|
| 427 |
if current_expires_at < datetime.now(timezone.utc):
|
| 428 |
# 如果已過期,從今天開始計算
|
| 429 |
today = datetime.now(timezone.utc).date()
|
| 430 |
-
start_date = datetime.combine(today, datetime.min.time(), timezone.utc)
|
| 431 |
new_expires_at = start_date + timedelta(days=extend_days) - timedelta(seconds=1)
|
| 432 |
else:
|
| 433 |
# 如果未過期,從原到期日的下一天開始計算
|
| 434 |
expire_date = current_expires_at.date()
|
| 435 |
next_day = expire_date + timedelta(days=1)
|
| 436 |
-
start_date = datetime.combine(next_day, datetime.min.time(), timezone.utc)
|
| 437 |
new_expires_at = start_date + timedelta(days=extend_days) - timedelta(seconds=1)
|
| 438 |
|
| 439 |
# 更新授權
|
|
|
|
| 37 |
|
| 38 |
license_code = self.generate_license_code()
|
| 39 |
|
| 40 |
+
# 計算到期時間:從當天00:00開始計算指定天數(-8hr對應台灣時間)
|
| 41 |
if license_data.expires_days > 0:
|
| 42 |
+
# 取得今天的午夜(00:00:00)作為起始時間,減8小時
|
| 43 |
today = datetime.now(timezone.utc).date()
|
| 44 |
+
start_date = datetime.combine(today, datetime.min.time(), timezone.utc) - timedelta(hours=8)
|
| 45 |
# 加上指定天數,到期日為最後一天的23:59:59
|
| 46 |
expires_at = start_date + timedelta(days=license_data.expires_days) - timedelta(seconds=1)
|
| 47 |
else:
|
|
|
|
| 422 |
license_data = result.data[0]
|
| 423 |
current_expires_at = datetime.fromisoformat(license_data["expires_at"].replace('Z', '+00:00'))
|
| 424 |
|
| 425 |
+
# 計算新的到期時間:從原到期日開始加天數(-8hr對應台灣時間)
|
|
|
|
| 426 |
if current_expires_at < datetime.now(timezone.utc):
|
| 427 |
# 如果已過期,從今天開始計算
|
| 428 |
today = datetime.now(timezone.utc).date()
|
| 429 |
+
start_date = datetime.combine(today, datetime.min.time(), timezone.utc) - timedelta(hours=8)
|
| 430 |
new_expires_at = start_date + timedelta(days=extend_days) - timedelta(seconds=1)
|
| 431 |
else:
|
| 432 |
# 如果未過期,從原到期日的下一天開始計算
|
| 433 |
expire_date = current_expires_at.date()
|
| 434 |
next_day = expire_date + timedelta(days=1)
|
| 435 |
+
start_date = datetime.combine(next_day, datetime.min.time(), timezone.utc) - timedelta(hours=8)
|
| 436 |
new_expires_at = start_date + timedelta(days=extend_days) - timedelta(seconds=1)
|
| 437 |
|
| 438 |
# 更新授權
|
supabase-schema.sql
CHANGED
|
@@ -145,9 +145,13 @@ COMMENT ON VIEW license_details IS '授權詳細資訊視圖 (含統計)';
|
|
| 145 |
-- =====================================================
|
| 146 |
-- 視圖 - 活動記錄 (前端儀表板使用)
|
| 147 |
-- =====================================================
|
| 148 |
-
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
| 150 |
ul.id,
|
|
|
|
| 151 |
ul.action,
|
| 152 |
ul.ip_address,
|
| 153 |
ul.user_agent,
|
|
@@ -157,18 +161,18 @@ SELECT
|
|
| 157 |
l.license_code,
|
| 158 |
l.user_name,
|
| 159 |
l.user_email,
|
| 160 |
-
CASE
|
| 161 |
-- 管理員操作:直接顯示為成功,不判斷 error_message
|
| 162 |
WHEN ul.action IN ('create', 'extend', 'activate_license', 'deactivate_license', 'delete') THEN ul.action
|
| 163 |
-- Plugin 操作:有 error_message 就是錯誤
|
| 164 |
WHEN ul.action IN ('activate', 'validate') THEN
|
| 165 |
-
CASE
|
| 166 |
WHEN ul.error_message IS NOT NULL THEN 'error'
|
| 167 |
ELSE ul.action
|
| 168 |
END
|
| 169 |
-- 其他操作:保持原邏輯
|
| 170 |
-
ELSE
|
| 171 |
-
CASE
|
| 172 |
WHEN ul.error_message IS NOT NULL THEN 'error'
|
| 173 |
ELSE ul.action
|
| 174 |
END
|
|
@@ -256,30 +260,30 @@ COMMENT ON FUNCTION generate_license_report IS '生成指定日期範圍的授
|
|
| 256 |
-- 示例資料 (可選)
|
| 257 |
-- =====================================================
|
| 258 |
|
| 259 |
-
-- 插入測試用授權 (啟用來測試系統)
|
| 260 |
INSERT INTO licenses (license_code, user_name, user_email, expires_at, hardware_id, is_active, activated_at) VALUES
|
| 261 |
-
-- 已啟用用戶 (正常使用中)
|
| 262 |
-
('A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6', '王小明', 'ming@company.com.tw', DATE_TRUNC('day', NOW()) + INTERVAL '90 days' - INTERVAL '1 second', 'CPU123ABC456DEF789GHI012JKL345MN', true, NOW() - INTERVAL '5 days'),
|
| 263 |
-
('X7Y8Z9W0Q1R2S3T4U5V6A7B8C9D0E1F2', '李美華', 'li.meihua@design.studio', DATE_TRUNC('day', NOW()) + INTERVAL '365 days' - INTERVAL '1 second', 'CPU789XYZ012ABC345DEF678GHI901JK', true, NOW() - INTERVAL '30 days'),
|
| 264 |
|
| 265 |
-
-- 試用用戶 (7天試用)
|
| 266 |
-
('B9C8D7E6F5G4H3I2J1K0L9M8N7O6P5Q4', '張工程師', 'engineer.zhang@tech.com', DATE_TRUNC('day', NOW()) + INTERVAL '7 days' - INTERVAL '1 second', 'CPUABC123XYZ456QWE789RTY012UIU34', true, NOW() - INTERVAL '2 days'),
|
| 267 |
|
| 268 |
-
-- 更多正常用戶
|
| 269 |
-
('M5N6O7P8Q9R0S1T2U3V4W5X6Y7Z8A9B0', '陳建築師', 'chen@architect.firm', DATE_TRUNC('day', NOW()) + INTERVAL '30 days' - INTERVAL '1 second', 'CPUARCH456BUILD789DESIGN012PROJ34', true, NOW() - INTERVAL '1 day'),
|
| 270 |
-
('F3G4H5I6J7K8L9M0N1O2P3Q4R5S6T7U8', '劉設計師', 'liu.designer@studio.tw', DATE_TRUNC('day', NOW()) + INTERVAL '60 days' - INTERVAL '1 second', 'CPUDESIGN123CREATIVE456ART789STUD', true, NOW() - INTERVAL '7 days'),
|
| 271 |
|
| 272 |
-- 過期授權 (已過期15天)
|
| 273 |
-
('Z0Y9X8W7V6U5T4S3R2Q1P0O9N8M7L6K5', '周老闆', 'boss.zhou@construction.co', DATE_TRUNC('day', NOW()) - INTERVAL '15 days' - INTERVAL '1 second', 'CPU456DEF789ABC012GHI345JKL678MN', true, NOW() - INTERVAL '45 days'),
|
| 274 |
|
| 275 |
-- 已停用授權 (管理員手動停用)
|
| 276 |
-
('Q2W3E4R5T6Y7U8I9O0P1A2S3D4F5G6H7', '黃
|
| 277 |
|
| 278 |
-- 永久授權 (企業用戶) - 50年
|
| 279 |
-
('K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6', 'KSTools管理員', 'admin@kstools.com.tw', DATE_TRUNC('day', NOW()) + INTERVAL '18250 days' - INTERVAL '1 second', 'CPUADMIN123MASTER456CONTROL789AB', true, NOW() - INTERVAL '1 day'),
|
| 280 |
|
| 281 |
-- 即將過期 (今天結束就過期)
|
| 282 |
-
('V8W9X0Y1Z2A3B4C5D6E7F8G9H0I1J2K3', '趙用戶', 'zhao@expires.soon', DATE_TRUNC('day', NOW()) + INTERVAL '1 day' - INTERVAL '1 second', 'CPU999END888SOON777EXPIRE666ABC', true, NOW() - INTERVAL '28 days')
|
| 283 |
ON CONFLICT (license_code) DO NOTHING;
|
| 284 |
|
| 285 |
-- 插入測試使用記錄
|
|
|
|
| 145 |
-- =====================================================
|
| 146 |
-- 視圖 - 活動記錄 (前端儀表板使用)
|
| 147 |
-- =====================================================
|
| 148 |
+
-- 重新創建 activity_logs 視圖
|
| 149 |
+
DROP VIEW IF EXISTS activity_logs;
|
| 150 |
+
|
| 151 |
+
CREATE VIEW activity_logs AS
|
| 152 |
+
SELECT
|
| 153 |
ul.id,
|
| 154 |
+
ul.license_id,
|
| 155 |
ul.action,
|
| 156 |
ul.ip_address,
|
| 157 |
ul.user_agent,
|
|
|
|
| 161 |
l.license_code,
|
| 162 |
l.user_name,
|
| 163 |
l.user_email,
|
| 164 |
+
CASE
|
| 165 |
-- 管理員操作:直接顯示為成功,不判斷 error_message
|
| 166 |
WHEN ul.action IN ('create', 'extend', 'activate_license', 'deactivate_license', 'delete') THEN ul.action
|
| 167 |
-- Plugin 操作:有 error_message 就是錯誤
|
| 168 |
WHEN ul.action IN ('activate', 'validate') THEN
|
| 169 |
+
CASE
|
| 170 |
WHEN ul.error_message IS NOT NULL THEN 'error'
|
| 171 |
ELSE ul.action
|
| 172 |
END
|
| 173 |
-- 其他操作:保持原邏輯
|
| 174 |
+
ELSE
|
| 175 |
+
CASE
|
| 176 |
WHEN ul.error_message IS NOT NULL THEN 'error'
|
| 177 |
ELSE ul.action
|
| 178 |
END
|
|
|
|
| 260 |
-- 示例資料 (可選)
|
| 261 |
-- =====================================================
|
| 262 |
|
| 263 |
+
-- 插入測試用授權 (啟用來測試系統) - 統一使用台灣時間00:00計算(-8hr)
|
| 264 |
INSERT INTO licenses (license_code, user_name, user_email, expires_at, hardware_id, is_active, activated_at) VALUES
|
| 265 |
+
-- 已啟用用戶 (正常使用中)
|
| 266 |
+
('A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6', '王小明', 'ming@company.com.tw', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '90 days' - INTERVAL '1 second', 'CPU123ABC456DEF789GHI012JKL345MN', true, NOW() - INTERVAL '5 days'),
|
| 267 |
+
('X7Y8Z9W0Q1R2S3T4U5V6A7B8C9D0E1F2', '李美華', 'li.meihua@design.studio', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '365 days' - INTERVAL '1 second', 'CPU789XYZ012ABC345DEF678GHI901JK', true, NOW() - INTERVAL '30 days'),
|
| 268 |
|
| 269 |
+
-- 試用用戶 (7天試用)
|
| 270 |
+
('B9C8D7E6F5G4H3I2J1K0L9M8N7O6P5Q4', '張工程師', 'engineer.zhang@tech.com', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '7 days' - INTERVAL '1 second', 'CPUABC123XYZ456QWE789RTY012UIU34', true, NOW() - INTERVAL '2 days'),
|
| 271 |
|
| 272 |
+
-- 更多正常用戶
|
| 273 |
+
('M5N6O7P8Q9R0S1T2U3V4W5X6Y7Z8A9B0', '陳建築師', 'chen@architect.firm', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '30 days' - INTERVAL '1 second', 'CPUARCH456BUILD789DESIGN012PROJ34', true, NOW() - INTERVAL '1 day'),
|
| 274 |
+
('F3G4H5I6J7K8L9M0N1O2P3Q4R5S6T7U8', '劉設計師', 'liu.designer@studio.tw', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '60 days' - INTERVAL '1 second', 'CPUDESIGN123CREATIVE456ART789STUD', true, NOW() - INTERVAL '7 days'),
|
| 275 |
|
| 276 |
-- 過期授權 (已過期15天)
|
| 277 |
+
('Z0Y9X8W7V6U5T4S3R2Q1P0O9N8M7L6K5', '周老闆', 'boss.zhou@construction.co', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' - INTERVAL '15 days' - INTERVAL '1 second', 'CPU456DEF789ABC012GHI345JKL678MN', true, NOW() - INTERVAL '45 days'),
|
| 278 |
|
| 279 |
-- 已停用授權 (管理員手動停用)
|
| 280 |
+
('Q2W3E4R5T6Y7U8I9O0P1A2S3D4F5G6H7', '黃��規用戶', 'huang@suspended.user', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '20 days' - INTERVAL '1 second', 'CPUXYZ789ABC012DEF345GHI678JKL90', false, NOW() - INTERVAL '10 days'),
|
| 281 |
|
| 282 |
-- 永久授權 (企業用戶) - 50年
|
| 283 |
+
('K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6', 'KSTools管理員', 'admin@kstools.com.tw', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '18250 days' - INTERVAL '1 second', 'CPUADMIN123MASTER456CONTROL789AB', true, NOW() - INTERVAL '1 day'),
|
| 284 |
|
| 285 |
-- 即將過期 (今天結束就過期)
|
| 286 |
+
('V8W9X0Y1Z2A3B4C5D6E7F8G9H0I1J2K3', '趙用戶', 'zhao@expires.soon', DATE_TRUNC('day', NOW()) - INTERVAL '8 hours' + INTERVAL '1 day' - INTERVAL '1 second', 'CPU999END888SOON777EXPIRE666ABC', true, NOW() - INTERVAL '28 days')
|
| 287 |
ON CONFLICT (license_code) DO NOTHING;
|
| 288 |
|
| 289 |
-- 插入測試使用記錄
|