同步客服充值渠道配置
This commit is contained in:
@@ -13,8 +13,9 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
|
|
||||||
from web.backend.database import Base
|
from web.backend.database import Base
|
||||||
from web.backend.models import Account, AuditLog, DouyuTask, User
|
from web.backend.models import Account, AuditLog, DouyuTask, User
|
||||||
from web.backend.routers.douyu import supplier_recharge_callback
|
from web.backend.routers.douyu import get_recharge_channel, supplier_recharge_callback
|
||||||
from web.backend.services.douyu_runner import DouyuBatchRunner
|
from web.backend.services.douyu_runner import DouyuBatchRunner
|
||||||
|
from web.backend.services.douyu_service import ensure_douyu_config
|
||||||
from web.backend.services.audit_service import record_audit
|
from web.backend.services.audit_service import record_audit
|
||||||
from core.douyu import FishFinRechargeClient, FishFinRechargeConfig
|
from core.douyu import FishFinRechargeClient, FishFinRechargeConfig
|
||||||
|
|
||||||
@@ -124,6 +125,18 @@ class DouyuGoldRechargeChannelTests(unittest.TestCase):
|
|||||||
"DYGFHISTORICAL-001",
|
"DYGFHISTORICAL-001",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_recharge_channel_can_be_read_without_full_config(self):
|
||||||
|
config = ensure_douyu_config(self.session)
|
||||||
|
config.gold_recharge_channel = "supplier_api"
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
channel = get_recharge_channel(
|
||||||
|
db=self.session,
|
||||||
|
current=self.session.get(User, self.task.created_by),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(channel, {"gold_recharge_channel": "supplier_api"})
|
||||||
|
|
||||||
def test_recharge_audit_detail_includes_safe_account_identity(self):
|
def test_recharge_audit_detail_includes_safe_account_identity(self):
|
||||||
record_audit(
|
record_audit(
|
||||||
self.session,
|
self.session,
|
||||||
|
|||||||
@@ -406,6 +406,20 @@ def get_config(
|
|||||||
return _config_out(ensure_douyu_config(db))
|
return _config_out(ensure_douyu_config(db))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/recharge-channel")
|
||||||
|
def get_recharge_channel(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current: User = Depends(require_permission("douyu:task")),
|
||||||
|
):
|
||||||
|
"""返回当前鱼翅充值渠道,不暴露供应商商品等配置细节。"""
|
||||||
|
config = ensure_douyu_config(db)
|
||||||
|
return {
|
||||||
|
"gold_recharge_channel": douyu_config_value(
|
||||||
|
"gold_recharge_channel", config.gold_recharge_channel,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.put("/config", response_model=DouyuConfigOut)
|
@router.put("/config", response_model=DouyuConfigOut)
|
||||||
def update_config(
|
def update_config(
|
||||||
req: DouyuConfigUpdate,
|
req: DouyuConfigUpdate,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import api from './client';
|
|||||||
import type {
|
import type {
|
||||||
DouyuConfig,
|
DouyuConfig,
|
||||||
DouyuGoodsItem,
|
DouyuGoodsItem,
|
||||||
|
DouyuRechargeChannel,
|
||||||
DouyuTaskAccountItem,
|
DouyuTaskAccountItem,
|
||||||
DouyuTaskBatchRequest,
|
DouyuTaskBatchRequest,
|
||||||
DouyuTaskBatchResult,
|
DouyuTaskBatchResult,
|
||||||
@@ -21,6 +22,7 @@ export const douyuApi = {
|
|||||||
listAccountIds: (params?: { search?: string; tag?: string }) =>
|
listAccountIds: (params?: { search?: string; tag?: string }) =>
|
||||||
api.get<{ account_ids: number[]; total: number }, { account_ids: number[]; total: number }>('/douyu/accounts/ids', { params }),
|
api.get<{ account_ids: number[]; total: number }, { account_ids: number[]; total: number }>('/douyu/accounts/ids', { params }),
|
||||||
getConfig: () => api.get<DouyuConfig, DouyuConfig>('/douyu/config'),
|
getConfig: () => api.get<DouyuConfig, DouyuConfig>('/douyu/config'),
|
||||||
|
getRechargeChannel: () => api.get<DouyuRechargeChannel, DouyuRechargeChannel>('/douyu/recharge-channel'),
|
||||||
updateConfig: (data: Partial<DouyuConfig>) => api.put<DouyuConfig, DouyuConfig>('/douyu/config', data),
|
updateConfig: (data: Partial<DouyuConfig>) => api.put<DouyuConfig, DouyuConfig>('/douyu/config', data),
|
||||||
listGoods: () => api.get<DouyuGoodsItem[], DouyuGoodsItem[]>('/douyu/goods'),
|
listGoods: () => api.get<DouyuGoodsItem[], DouyuGoodsItem[]>('/douyu/goods'),
|
||||||
listEsportsGoods: () => api.get<DouyuGoodsItem[], DouyuGoodsItem[]>('/douyu/esports-goods'),
|
listEsportsGoods: () => api.get<DouyuGoodsItem[], DouyuGoodsItem[]>('/douyu/esports-goods'),
|
||||||
|
|||||||
@@ -382,6 +382,10 @@ export interface DouyuConfig {
|
|||||||
updated_at: string | null;
|
updated_at: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DouyuRechargeChannel {
|
||||||
|
gold_recharge_channel: 'wechat_qr' | 'supplier_api';
|
||||||
|
}
|
||||||
|
|
||||||
export interface DouyuTaskBatchRequest {
|
export interface DouyuTaskBatchRequest {
|
||||||
account_ids: number[];
|
account_ids: number[];
|
||||||
task_type: string;
|
task_type: string;
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
const [tasks, setTasks] = useState<DouyuTaskItem[]>([]);
|
const [tasks, setTasks] = useState<DouyuTaskItem[]>([]);
|
||||||
const [taskTypes, setTaskTypes] = useState<Record<string, string>>({});
|
const [taskTypes, setTaskTypes] = useState<Record<string, string>>({});
|
||||||
const [config, setConfig] = useState<DouyuConfig | null>(null);
|
const [config, setConfig] = useState<DouyuConfig | null>(null);
|
||||||
|
const [goldRechargeChannel, setGoldRechargeChannel] = useState<DouyuConfig['gold_recharge_channel'] | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [runningBatchIds, setRunningBatchIds] = useState<Set<string>>(new Set());
|
const [runningBatchIds, setRunningBatchIds] = useState<Set<string>>(new Set());
|
||||||
const [configOpen, setConfigOpen] = useState(false);
|
const [configOpen, setConfigOpen] = useState(false);
|
||||||
@@ -580,7 +581,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const [accResult, goodsResult, taskResult, cfgResult, typeResult] = await Promise.allSettled([
|
const [accResult, goodsResult, taskResult, cfgResult, typeResult, rechargeChannelResult] = await Promise.allSettled([
|
||||||
workbenchIds.length > 0
|
workbenchIds.length > 0
|
||||||
? douyuApi.listAccountsPaged({
|
? douyuApi.listAccountsPaged({
|
||||||
ids: workbenchIds.join(','),
|
ids: workbenchIds.join(','),
|
||||||
@@ -595,6 +596,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
douyuApi.listTasks(handbook),
|
douyuApi.listTasks(handbook),
|
||||||
canConfig ? douyuApi.getConfig() : Promise.resolve(null),
|
canConfig ? douyuApi.getConfig() : Promise.resolve(null),
|
||||||
douyuApi.taskTypes(),
|
douyuApi.taskTypes(),
|
||||||
|
douyuApi.getRechargeChannel(),
|
||||||
]);
|
]);
|
||||||
if (accResult.status === 'fulfilled') {
|
if (accResult.status === 'fulfilled') {
|
||||||
setAccounts(accResult.value.items);
|
setAccounts(accResult.value.items);
|
||||||
@@ -627,6 +629,9 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
}
|
}
|
||||||
if (cfgResult.status === 'fulfilled' && cfgResult.value) setConfig(cfgResult.value);
|
if (cfgResult.status === 'fulfilled' && cfgResult.value) setConfig(cfgResult.value);
|
||||||
if (typeResult.status === 'fulfilled') setTaskTypes(typeResult.value);
|
if (typeResult.status === 'fulfilled') setTaskTypes(typeResult.value);
|
||||||
|
if (rechargeChannelResult.status === 'fulfilled') {
|
||||||
|
setGoldRechargeChannel(rechargeChannelResult.value.gold_recharge_channel);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -1031,6 +1036,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
try {
|
try {
|
||||||
const saved = await douyuApi.updateConfig(values);
|
const saved = await douyuApi.updateConfig(values);
|
||||||
setConfig(saved);
|
setConfig(saved);
|
||||||
|
setGoldRechargeChannel(saved.gold_recharge_channel);
|
||||||
setConfigOpen(false);
|
setConfigOpen(false);
|
||||||
message.success('配置已保存');
|
message.success('配置已保存');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1627,9 +1633,14 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
|
|
||||||
const payUrl = resultText(payTask?.result, 'pay_url');
|
const payUrl = resultText(payTask?.result, 'pay_url');
|
||||||
const goldAmountLabel = '充值金额';
|
const goldAmountLabel = '充值金额';
|
||||||
const isSupplierGoldRecharge = config?.gold_recharge_channel === 'supplier_api';
|
const isRechargeChannelLoading = goldRechargeChannel === null;
|
||||||
const goldRechargeChannelLabel = isSupplierGoldRecharge ? '供应商 API 直充' : '微信扫码';
|
const isSupplierGoldRecharge = goldRechargeChannel === 'supplier_api';
|
||||||
const goldRechargeButtonLabel = isSupplierGoldRecharge ? 'API直充鱼翅' : '扫码充值鱼翅';
|
const goldRechargeChannelLabel = isRechargeChannelLoading
|
||||||
|
? '加载中'
|
||||||
|
: (isSupplierGoldRecharge ? '供应商 API 直充' : '微信扫码');
|
||||||
|
const goldRechargeButtonLabel = isRechargeChannelLoading
|
||||||
|
? '加载中'
|
||||||
|
: (isSupplierGoldRecharge ? 'API直充鱼翅' : '扫码充值鱼翅');
|
||||||
const payAccountName = payTask
|
const payAccountName = payTask
|
||||||
? (payTask.account_nickname || payTask.account_username || payTask.account_uid || `#${payTask.account_id}`)
|
? (payTask.account_nickname || payTask.account_username || payTask.account_uid || `#${payTask.account_id}`)
|
||||||
: '';
|
: '';
|
||||||
@@ -1803,7 +1814,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
size="small"
|
size="small"
|
||||||
icon={<CreditCardOutlined />}
|
icon={<CreditCardOutlined />}
|
||||||
onClick={() => startTask('create_gold_qr')}
|
onClick={() => startTask('create_gold_qr')}
|
||||||
disabled={actionDisabled('create_gold_qr')}
|
disabled={isRechargeChannelLoading || actionDisabled('create_gold_qr')}
|
||||||
>
|
>
|
||||||
{goldRechargeButtonLabel}
|
{goldRechargeButtonLabel}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1869,7 +1880,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
size="small"
|
size="small"
|
||||||
icon={<CreditCardOutlined />}
|
icon={<CreditCardOutlined />}
|
||||||
onClick={() => startTask('create_gold_qr')}
|
onClick={() => startTask('create_gold_qr')}
|
||||||
disabled={actionDisabled('create_gold_qr')}
|
disabled={isRechargeChannelLoading || actionDisabled('create_gold_qr')}
|
||||||
>
|
>
|
||||||
{goldRechargeButtonLabel}
|
{goldRechargeButtonLabel}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user