feat(points): 积分流水表支持时长显示,说明字段简化
后端: - PointTransaction 模型添加 duration 字段(float, nullable) - PointTransactionItem schema 添加 duration - consume() 新增 duration 参数,写入流水记录 - 各业务 description 统一简化为【脚本生成】【配音合成】等格式 - duration 类业务(tts/video)传入实际秒数 - Alembic 迁移: 95eb1a1c0af9_add_duration_to_point_transaction 前端: - PointTransaction 类型添加 duration - UsageDetail: 来源列 → 时长列(有值显示 xs,无值显示 -) - 说明列直接显示后端返回的简化描述
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
积分流水表添加 duration 字段
|
||||
|
||||
用于记录按秒计费业务的时长(TTS、数字人视频等)。
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "95eb1a1c0af9"
|
||||
down_revision: Union[str, None] = "ccf61ff6f4bb"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"mjk_point_transactions",
|
||||
sa.Column("duration", sa.Float(), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("mjk_point_transactions", "duration")
|
||||
@@ -444,7 +444,7 @@ async def consume_points(
|
||||
points=request.points,
|
||||
source_type=request.source_type,
|
||||
source_id=request.source_id,
|
||||
description=request.description,
|
||||
description=f"【{request.description or request.source_type}】",
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ async def polish_content(
|
||||
points=points,
|
||||
source_type="polish",
|
||||
source_id=f"polish_{current_user.id}_{asyncio.get_event_loop().time()}",
|
||||
description=f"润色 {request.polish_type}",
|
||||
description="【文案润色】",
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
@@ -211,7 +211,7 @@ async def generate_title(
|
||||
points=points,
|
||||
source_type="title",
|
||||
source_id=f"title_{current_user.id}_{asyncio.get_event_loop().time()}",
|
||||
description=f"生成{request.title_type}标题",
|
||||
description="【标题生成】",
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
|
||||
@@ -231,7 +231,8 @@ async def synthesize_speech(
|
||||
points=points,
|
||||
source_type="tts",
|
||||
source_id=f"tts_{current_user.id}_{asyncio.get_event_loop().time()}",
|
||||
description=f"TTS 配音 {seconds:.1f} 秒",
|
||||
description="【配音合成】",
|
||||
duration=seconds,
|
||||
)
|
||||
await db.commit()
|
||||
except Exception as e:
|
||||
@@ -317,7 +318,8 @@ async def synthesize_batch(
|
||||
points=points,
|
||||
source_type="tts",
|
||||
source_id=f"tts_batch_{current_user.id}_{asyncio.get_event_loop().time()}",
|
||||
description=f"批量 TTS 配音 {total_seconds:.1f} 秒",
|
||||
description="【配音合成】",
|
||||
duration=total_seconds,
|
||||
)
|
||||
await db.commit()
|
||||
except Exception as e:
|
||||
@@ -448,7 +450,7 @@ async def submit_clone_task(
|
||||
points=points,
|
||||
source_type="voice_clone",
|
||||
source_id=result.get("voice_id", "unknown"),
|
||||
description="声音克隆",
|
||||
description="【声音克隆】",
|
||||
)
|
||||
await db.commit()
|
||||
except Exception as e:
|
||||
|
||||
@@ -73,6 +73,11 @@ class PointTransaction(BaseModelBigInt):
|
||||
comment="关联的积分批次 ID(消费时记录从哪个批次扣)",
|
||||
)
|
||||
|
||||
duration: Mapped[float | None] = mapped_column(
|
||||
nullable=True,
|
||||
comment="时长(秒),按秒计费业务记录",
|
||||
)
|
||||
|
||||
description: Mapped[str | None] = mapped_column(
|
||||
Text,
|
||||
nullable=True,
|
||||
|
||||
@@ -145,7 +145,7 @@ class ScriptHandler(AsyncHandler):
|
||||
points=points,
|
||||
source_type="script",
|
||||
source_id=task.task_id,
|
||||
description=f"脚本生成 {category}/{subcategory}",
|
||||
description="【脚本生成】",
|
||||
)
|
||||
await db.commit()
|
||||
except Exception as e:
|
||||
|
||||
@@ -78,7 +78,8 @@ class VideoHandler(AsyncHandler):
|
||||
points=points,
|
||||
source_type="video",
|
||||
source_id=task.task_id,
|
||||
description=f"对口型视频 {duration:.1f} 秒",
|
||||
description="【数字人视频】",
|
||||
duration=duration,
|
||||
)
|
||||
await db.commit()
|
||||
except Exception as e:
|
||||
@@ -135,7 +136,8 @@ class VideoHandler(AsyncHandler):
|
||||
points=points,
|
||||
source_type="video",
|
||||
source_id=task.task_id,
|
||||
description=f"对口型视频 {duration:.1f} 秒",
|
||||
description="【数字人视频】",
|
||||
duration=duration,
|
||||
)
|
||||
await db.commit()
|
||||
except Exception as e:
|
||||
|
||||
@@ -32,6 +32,7 @@ class PointTransactionItem(BaseModel):
|
||||
balance_after: int
|
||||
source_type: str | None = Field(None, description="消费来源类型")
|
||||
source_id: str | None = Field(None, description="消费来源业务 ID")
|
||||
duration: float | None = Field(None, description="时长(秒),按秒计费业务记录")
|
||||
description: str | None = None
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -272,6 +272,7 @@ async def consume(
|
||||
source_type: str,
|
||||
source_id: str,
|
||||
description: str = "",
|
||||
duration: float | None = None,
|
||||
) -> PointTransaction:
|
||||
"""
|
||||
直接扣费(后置计费)。
|
||||
@@ -343,6 +344,7 @@ async def consume(
|
||||
source_type=source_type,
|
||||
source_id=source_id,
|
||||
batch_id=batches[0].id if batches else None,
|
||||
duration=duration,
|
||||
description=description or f"消费 {source_type} {points} 积分",
|
||||
)
|
||||
db.add(tx)
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface PointTransaction {
|
||||
balanceAfter: number;
|
||||
sourceType: string | null;
|
||||
sourceId: string | null;
|
||||
duration: number | null;
|
||||
description: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export default function UsageDetail() {
|
||||
<th>变动积分</th>
|
||||
<th>变动前余额</th>
|
||||
<th>变动后余额</th>
|
||||
<th>来源</th>
|
||||
<th>时长</th>
|
||||
<th>说明</th>
|
||||
<th>时间</th>
|
||||
</tr>
|
||||
@@ -149,7 +149,7 @@ export default function UsageDetail() {
|
||||
</td>
|
||||
<td>{tx.balanceBefore}</td>
|
||||
<td>{tx.balanceAfter}</td>
|
||||
<td>{tx.sourceType || '-'}</td>
|
||||
<td>{tx.duration != null ? `${tx.duration.toFixed(1)}s` : '-'}</td>
|
||||
<td>{tx.description || '-'}</td>
|
||||
<td>{formatTime(tx.createdAt)}</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user