增加预扣积分
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+36
-36
@@ -1,37 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<title>后台管理</title>
|
||||
<script>
|
||||
(function() {
|
||||
var cached = localStorage.getItem('siteInfo');
|
||||
if (cached) {
|
||||
try {
|
||||
var info = JSON.parse(cached);
|
||||
if (info.siteName) {
|
||||
document.title = info.siteName + ' - 管理后台';
|
||||
}
|
||||
if (info.siteLogo) {
|
||||
var link = document.querySelector('link[rel="icon"]');
|
||||
if (link) {
|
||||
link.href = info.siteLogo;
|
||||
link.type = 'image/png';
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-BLkGzXo7.js"></script>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<title>后台管理</title>
|
||||
<script>
|
||||
(function() {
|
||||
var cached = localStorage.getItem('siteInfo');
|
||||
if (cached) {
|
||||
try {
|
||||
var info = JSON.parse(cached);
|
||||
if (info.siteName) {
|
||||
document.title = info.siteName + ' - 管理后台';
|
||||
}
|
||||
if (info.siteLogo) {
|
||||
var link = document.querySelector('link[rel="icon"]');
|
||||
if (link) {
|
||||
link.href = info.siteLogo;
|
||||
link.type = 'image/png';
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-CtNUj_vl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>␍
|
||||
</body>
|
||||
</html>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -178,7 +178,7 @@ const AdminSettings: React.FC = () => {
|
||||
'协议配置': configs.filter(c => c.key === 'user_agreement_privacy_url'),
|
||||
'SEO 设置': configs.filter(c => c.key.startsWith('seo_')),
|
||||
'用户积分配置': configs.filter(c => c.key.startsWith('user_') && c.key.includes('credits')),
|
||||
'其他配置': configs.filter(c => c.key === 'operation_manual'),
|
||||
'其他配置': configs.filter(c => ['operation_manual', 'optimize_hold_credits'].includes(c.key)),
|
||||
};
|
||||
|
||||
const getFieldDescription = (config: SystemConfig): string => {
|
||||
@@ -194,6 +194,7 @@ const AdminSettings: React.FC = () => {
|
||||
user_login_credits: '用户每日登录赠送的积分数量',
|
||||
user_login_credits_enabled: '是否启用每日登录赠送积分功能',
|
||||
operation_manual: '操作手册链接,前台用户菜单将展示该入口,点击跳转此链接',
|
||||
optimize_hold_credits: 'AI创作时预扣积分数量,用于防止并发超卖。预扣后按实际消耗多退少补',
|
||||
};
|
||||
return descMap[config.key] || config.description || '';
|
||||
};
|
||||
@@ -323,8 +324,8 @@ const AdminSettings: React.FC = () => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (config.key === 'user_register_credits' || config.key === 'user_login_credits') {
|
||||
return <Input type="number" min={0} placeholder={config.description} size="large" />;
|
||||
if (config.key === 'user_register_credits' || config.key === 'user_login_credits' || config.key === 'optimize_hold_credits') {
|
||||
return <Input type="number" min={1} placeholder={config.description} size="large" />;
|
||||
}
|
||||
return <Input placeholder={config.description} size="large" />;
|
||||
};
|
||||
|
||||
@@ -342,7 +342,7 @@ async def get_site_info(db: AsyncSession = Depends(get_db)):
|
||||
"""Public endpoint returning site name, logo, agreement and copyright info."""
|
||||
result = await db.execute(
|
||||
select(SystemConfig).where(SystemConfig.key.in_([
|
||||
"site_name", "site_logo", "user_agreement_privacy_url", "site_copyright", "operation_manual", "login_bg_video"
|
||||
"site_name", "site_logo", "user_agreement_privacy_url", "site_copyright", "operation_manual", "login_bg_video", "optimize_hold_credits"
|
||||
]))
|
||||
)
|
||||
configs = result.scalars().all()
|
||||
@@ -366,6 +366,7 @@ async def get_site_info(db: AsyncSession = Depends(get_db)):
|
||||
"site_copyright": info.get("site_copyright", "© 2024 民众智创 版权所有"),
|
||||
"operation_manual": info.get("operation_manual", ""),
|
||||
"login_bg_video": to_full_url(info.get("login_bg_video")) if info.get("login_bg_video") else "",
|
||||
"optimize_hold_credits": int(info.get("optimize_hold_credits") or 5),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ from fastapi.responses import RedirectResponse
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.config import settings
|
||||
from app.dependencies import get_db, get_current_user
|
||||
from app.models.user import User
|
||||
from app.models.project import Project
|
||||
from app.models.generation_record import GenerationRecord
|
||||
from app.models.system_config import SystemConfig
|
||||
from app.schemas.generation import (
|
||||
OptimizeParams,
|
||||
GenerateParams,
|
||||
@@ -32,7 +32,7 @@ from app.services.generation.pipeline.db_lock_service import (
|
||||
DatabaseRowLockBusy,
|
||||
execute_with_lock_timeout,
|
||||
)
|
||||
from app.services.credits import deduct_credits, calc_text_credits
|
||||
from app.services.credits import deduct_credits, add_credits, calc_text_credits
|
||||
from app.services.llm import optimize_prompt
|
||||
from app.services.video_url import generate_temp_url, validate_and_get_record_id, get_video_stream_url
|
||||
from app.services.resource_accounting_service import (
|
||||
@@ -271,9 +271,17 @@ async def optimize(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
record = None
|
||||
# 积分不足直接返回
|
||||
if (current_user.credits or 0) < 5:
|
||||
raise HTTPException(status_code=402, detail="积分不足,请充值")
|
||||
# 预扣积分(SELECT FOR UPDATE 锁行 + 检查 + 扣费,防止并发超卖)
|
||||
hold_credits = 5
|
||||
_hold_cfg = await db.execute(select(SystemConfig).where(SystemConfig.key == "optimize_hold_credits").limit(1))
|
||||
_hold_row = _hold_cfg.scalar_one_or_none()
|
||||
if _hold_row and _hold_row.value:
|
||||
try:
|
||||
hold_credits = int(_hold_row.value)
|
||||
except (ValueError, TypeError):
|
||||
hold_credits = 5
|
||||
await deduct_credits(db, current_user.id, hold_credits, "AI创作预扣积分", biz_key=f"optimize_hold:{req.idempotency_key or 'nokey'}")
|
||||
await db.commit()
|
||||
# Validate parameters based on generation type
|
||||
if req.gen_type == GenerationType.video:
|
||||
if req.duration not in DURATIONS:
|
||||
@@ -377,6 +385,13 @@ async def optimize(
|
||||
db, token_usage["input_tokens"], token_usage["output_tokens"],
|
||||
)
|
||||
|
||||
# 积分结算:预扣金额按实际消耗多退少补
|
||||
if text_credits < hold_credits:
|
||||
await add_credits(db, current_user.id, round(hold_credits - text_credits, 2), f"提示词优化积分退还- {project.name}", biz_key=f"optimize_refund:{req.idempotency_key or 'nokey'}")
|
||||
elif text_credits > hold_credits:
|
||||
await deduct_credits(db, current_user.id, round(text_credits - hold_credits, 2), f"提示词优化补扣积分 - {project.name}", biz_key=f"optimize_topup:{req.idempotency_key or 'nokey'}")
|
||||
await db.commit()
|
||||
|
||||
failed_record_id = record.id
|
||||
failed_user_id = current_user.id
|
||||
try:
|
||||
|
||||
@@ -185,6 +185,7 @@ async def _seed_data():
|
||||
("user_login_credits_enabled", "false", "启用每日登录赠送积分"),
|
||||
# Operation manual
|
||||
("operation_manual", "", "操作手册链接"),
|
||||
("optimize_hold_credits", "5", "AI创作预扣积分数量(防止并发超卖)"),
|
||||
]
|
||||
for key, value, desc in configs:
|
||||
existing = await db.execute(
|
||||
|
||||
Reference in New Issue
Block a user