xiaolongmr commited on
Commit ·
5bee2d2
1
Parent(s): fbc3dbc
用量流水库接入存储桶快照持久化(与账号/卡密库同一机制)
Browse files
services/credit_ledger_service.py
CHANGED
|
@@ -30,14 +30,16 @@ except ImportError: # pragma: no cover - Python 3.9+ normally has zoneinfo.
|
|
| 30 |
ZoneInfo = None # type: ignore[assignment]
|
| 31 |
|
| 32 |
from services.config import DATA_DIR
|
|
|
|
| 33 |
|
| 34 |
-
# 落盘位置
|
| 35 |
-
#
|
| 36 |
-
#
|
| 37 |
-
#
|
|
|
|
| 38 |
LEDGER_FILE = Path(
|
| 39 |
str(os.getenv("CHATGPT2API_CREDIT_LEDGER_FILE") or "").strip()
|
| 40 |
-
or (DATA_DIR
|
| 41 |
)
|
| 42 |
|
| 43 |
_CHINA_TZ = ZoneInfo("Asia/Shanghai") if ZoneInfo else timezone(timedelta(hours=8))
|
|
|
|
| 30 |
ZoneInfo = None # type: ignore[assignment]
|
| 31 |
|
| 32 |
from services.config import DATA_DIR
|
| 33 |
+
from services.sqlite_mirror_service import sqlite_mirror_service
|
| 34 |
|
| 35 |
+
# 落盘位置:优先 CHATGPT2API_CREDIT_LEDGER_FILE,否则交给 sqlite 镜像服务 ——
|
| 36 |
+
# 与号池账号库(accounts.db)、用户 / 卡密库(users.db)完全同一套机制:
|
| 37 |
+
# 活库跑在容器本地磁盘,定时把通过完整性校验的快照写进挂载的存储桶
|
| 38 |
+
# (HF Storage Bucket 挂在 /app/persist),容器重建后启动时自动从最新快照恢复,
|
| 39 |
+
# 所以重新部署 / 重启都不会清空。
|
| 40 |
LEDGER_FILE = Path(
|
| 41 |
str(os.getenv("CHATGPT2API_CREDIT_LEDGER_FILE") or "").strip()
|
| 42 |
+
or sqlite_mirror_service.runtime_file(DATA_DIR, "credit_ledger.db")
|
| 43 |
)
|
| 44 |
|
| 45 |
_CHINA_TZ = ZoneInfo("Asia/Shanghai") if ZoneInfo else timezone(timedelta(hours=8))
|
services/sqlite_mirror_service.py
CHANGED
|
@@ -31,6 +31,12 @@ _SCHEMAS: dict[str, tuple[str, tuple[str, ...]]] = {
|
|
| 31 |
"CREATE TABLE IF NOT EXISTS shop_ledger (id INTEGER PRIMARY KEY AUTOINCREMENT, ledger_id VARCHAR(64) NOT NULL UNIQUE, created_at VARCHAR(64), user_id VARCHAR(255), data TEXT NOT NULL)",
|
| 32 |
("id", "ledger_id", "created_at", "user_id", "data"),
|
| 33 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
}
|
| 35 |
|
| 36 |
|
|
|
|
| 31 |
"CREATE TABLE IF NOT EXISTS shop_ledger (id INTEGER PRIMARY KEY AUTOINCREMENT, ledger_id VARCHAR(64) NOT NULL UNIQUE, created_at VARCHAR(64), user_id VARCHAR(255), data TEXT NOT NULL)",
|
| 32 |
("id", "ledger_id", "created_at", "user_id", "data"),
|
| 33 |
),
|
| 34 |
+
# 生图用量流水(data/credit_ledger.db)。它不在上面那几个库里,但同样跑在这套
|
| 35 |
+
# 镜像机制下;登记 schema 是为了数据库损坏时能被尽力抢救,而不是只能丢掉。
|
| 36 |
+
"credit_usage": (
|
| 37 |
+
"CREATE TABLE IF NOT EXISTS credit_usage (id INTEGER PRIMARY KEY AUTOINCREMENT, created_ts REAL NOT NULL, day TEXT NOT NULL, identity_id TEXT NOT NULL, role TEXT NOT NULL, model TEXT NOT NULL, status TEXT NOT NULL, image_count INTEGER NOT NULL DEFAULT 0, credits REAL NOT NULL DEFAULT 0, unit_price REAL NOT NULL DEFAULT 0, task_id TEXT NOT NULL DEFAULT '')",
|
| 38 |
+
("id", "created_ts", "day", "identity_id", "role", "model", "status", "image_count", "credits", "unit_price", "task_id"),
|
| 39 |
+
),
|
| 40 |
}
|
| 41 |
|
| 42 |
|
test/test_credit_ledger_service.py
CHANGED
|
@@ -164,19 +164,21 @@ class CreditLedgerServiceTests(unittest.TestCase):
|
|
| 164 |
|
| 165 |
|
| 166 |
class CreditLedgerPathTests(unittest.TestCase):
|
| 167 |
-
"""流水库必须
|
| 168 |
|
| 169 |
-
def
|
| 170 |
import importlib
|
| 171 |
import os
|
| 172 |
from unittest.mock import patch
|
| 173 |
|
| 174 |
import services.credit_ledger_service as module
|
|
|
|
| 175 |
|
| 176 |
try:
|
| 177 |
with patch.dict(os.environ, {"CHATGPT2API_CREDIT_LEDGER_FILE": ""}, clear=False):
|
| 178 |
reloaded = importlib.reload(module)
|
| 179 |
-
|
|
|
|
| 180 |
override = Path(tempfile.mkdtemp()) / "custom.db"
|
| 181 |
with patch.dict(os.environ, {"CHATGPT2API_CREDIT_LEDGER_FILE": str(override)}):
|
| 182 |
reloaded = importlib.reload(module)
|
|
|
|
| 164 |
|
| 165 |
|
| 166 |
class CreditLedgerPathTests(unittest.TestCase):
|
| 167 |
+
"""流水库必须与账号库 / 用户卡密库走同一套持久化(sqlite 镜像 → 存储桶)。"""
|
| 168 |
|
| 169 |
+
def test_path_uses_sqlite_mirror_and_env_override(self) -> None:
|
| 170 |
import importlib
|
| 171 |
import os
|
| 172 |
from unittest.mock import patch
|
| 173 |
|
| 174 |
import services.credit_ledger_service as module
|
| 175 |
+
from services.sqlite_mirror_service import sqlite_mirror_service
|
| 176 |
|
| 177 |
try:
|
| 178 |
with patch.dict(os.environ, {"CHATGPT2API_CREDIT_LEDGER_FILE": ""}, clear=False):
|
| 179 |
reloaded = importlib.reload(module)
|
| 180 |
+
expected = sqlite_mirror_service.runtime_file(reloaded.DATA_DIR, "credit_ledger.db")
|
| 181 |
+
self.assertEqual(reloaded.LEDGER_FILE, expected)
|
| 182 |
override = Path(tempfile.mkdtemp()) / "custom.db"
|
| 183 |
with patch.dict(os.environ, {"CHATGPT2API_CREDIT_LEDGER_FILE": str(override)}):
|
| 184 |
reloaded = importlib.reload(module)
|