交易流水对账明细导出完成

This commit is contained in:
2026-06-25 16:53:04 +08:00
parent 8aafd7de32
commit 30db7d3bf5
34 changed files with 2491 additions and 327 deletions
+37 -2
View File
@@ -1,4 +1,4 @@
from sqlalchemy import Float, ForeignKey, Index, String
from sqlalchemy import Float, ForeignKey, Index, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
@@ -12,13 +12,17 @@ class CreditRecord(Base, TimestampMixin):
Index("uq_credit_records_user_biz_key", "user_id", "biz_key", unique=True),
Index("ix_credit_records_user_refund_for_biz_key", "user_id", "refund_for_biz_key"),
Index("ix_credit_records_related_type", "related_id", "type"),
Index("ix_credit_records_owner", "owner_type", "owner_id"),
Index("ix_credit_records_subject_media", "credit_subject", "media_type"),
Index("ix_credit_records_source_module_scene", "source_module", "billing_scene"),
Index("ix_credit_records_user_kind_time", "user_type_snapshot", "frontend_user_kind_snapshot", "created_at"),
)
id: Mapped[str] = mapped_column(String(32), primary_key=True)
user_id: Mapped[str] = mapped_column(
String(32), ForeignKey("users.id", ondelete="CASCADE"), index=True
)
type: Mapped[str] = mapped_column(String(16))
type: Mapped[str] = mapped_column(String(16), index=True)
amount: Mapped[float] = mapped_column(Float)
balance_after: Mapped[float] = mapped_column(Float)
description: Mapped[str] = mapped_column(String(256))
@@ -31,3 +35,34 @@ class CreditRecord(Base, TimestampMixin):
# 如果当前流水是退款,记录它退的是哪一次扣费。
# 例如:generation_record:{record_id}:attempt:1:media:charge
refund_for_biz_key: Mapped[str | None] = mapped_column(String(160), nullable=True, index=True)
# 账务快照字段:保证业务步骤/资源软删后,流水仍可独立展示。
owner_type: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
owner_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
attempt_no: Mapped[int | None] = mapped_column(Integer, nullable=True)
charge_kind: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
charge_action: Mapped[str | None] = mapped_column(String(16), nullable=True)
credit_subject: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
media_type: Mapped[str | None] = mapped_column(String(16), nullable=True, index=True)
billing_scene: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
source_module: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
source_project_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
source_step_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
source_step_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
token_usage_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
input_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
output_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
total_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
engine_type: Mapped[str | None] = mapped_column(String(16), nullable=True)
engine_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
engine_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
engine_provider: Mapped[str | None] = mapped_column(String(64), nullable=True)
engine_model_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
user_type_snapshot: Mapped[str | None] = mapped_column(String(16), nullable=True, index=True)
frontend_user_kind_snapshot: Mapped[str | None] = mapped_column(String(16), nullable=True, index=True)
@@ -3,7 +3,7 @@ from __future__ import annotations
from datetime import datetime
from typing import Any
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Index, Integer, JSON, String, Text
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
@@ -72,3 +72,11 @@ class ModuleGenerationStep(Base, TimestampMixin, SoftDeleteMixin):
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
# 提词优化成本快照,避免后台/详情页反复解析 output_json.usage。
token_usage_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
model_config_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
input_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
output_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
total_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
text_credits_cost: Mapped[float | None] = mapped_column(Float, nullable=True)
+11 -3
View File
@@ -1,6 +1,4 @@
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Integer, String
from sqlalchemy import ForeignKey, Index, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
@@ -8,6 +6,10 @@ from app.models.base import Base, TimestampMixin
class TokenUsage(Base, TimestampMixin):
__tablename__ = "token_usage"
__table_args__ = (
Index("ix_token_usage_owner", "owner_type", "owner_id"),
Index("ix_token_usage_biz_key", "biz_key"),
)
id: Mapped[str] = mapped_column(String(32), primary_key=True)
model_config_id: Mapped[str | None] = mapped_column(
@@ -19,3 +21,9 @@ class TokenUsage(Base, TimestampMixin):
input_tokens: Mapped[int] = mapped_column(Integer, default=0)
output_tokens: Mapped[int] = mapped_column(Integer, default=0)
total_tokens: Mapped[int] = mapped_column(Integer, default=0)
owner_type: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
owner_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
biz_key: Mapped[str | None] = mapped_column(String(160), nullable=True, index=True)
source_module: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
source_step_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
+11 -2
View File
@@ -1,8 +1,9 @@
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Float, Integer, String, JSON
from sqlalchemy import Boolean, DateTime, Float, String, JSON
from sqlalchemy.orm import Mapped, mapped_column
from app.enums.user import FrontendUserKind
from app.models.base import Base, TimestampMixin
@@ -19,7 +20,15 @@ class User(Base, TimestampMixin):
credits: Mapped[float] = mapped_column(Float, default=0.0)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
user_type: Mapped[str] = mapped_column(String(16), default="frontend")
user_type: Mapped[str] = mapped_column(String(16), default="frontend", index=True)
# 仅前台用户有业务意义;默认外部用户。取消内部标记时也设置回 external。
frontend_user_kind: Mapped[str] = mapped_column(
String(16),
default=FrontendUserKind.EXTERNAL.value,
server_default=FrontendUserKind.EXTERNAL.value,
index=True,
nullable=False,
)
last_login_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)