feat(accounts): 创建账号表单支持指纹浏览器环境配置
douyin-release-gate / verify (push) Failing after 4m3s

- 前端创建页折叠区块:平台/品牌/版本/语言/时区/硬件并发/关闭伪装维度,留空使用默认
- 后端 accountRequest 增加 fingerprint,校验前置 400(避免账号已建、绑定失败的中间态)
- seed 服务端从账号派生、proxy 由网络出口管理,两者不接受表单值
- 补建端点支持携带指纹重试;start 自愈补建保持零值指纹
- PG 集成测试:指纹落库/非法值拒绝不建号/补建指纹重试
This commit is contained in:
2026-09-29 14:49:59 +08:00
parent a94bdf7921
commit a9ac84f459
4 changed files with 282 additions and 16 deletions
+126 -5
View File
@@ -1,19 +1,41 @@
// 创建社媒账号:语义对齐 web.archived AccountCreatePage + AccountCreateForm。
// cookies 非必填:留空代表创建后走扫码登录。创建成功跳编辑页。
// 折叠区块:指纹浏览器环境(可选);seed 由后端从账号派生,代理由网络出口管理,均不在表单内。
import { useState } from 'react';
import { history } from '@umijs/max';
import { Alert, Button, Card, Form, Input, Select } from 'antd';
import { Alert, Button, Card, Col, Collapse, Flex, Form, Input, InputNumber, Row, Select, Typography } from 'antd';
import { create } from '@/services/api';
import { conflictMessage, platforms } from '@/utils/helpers';
interface FingerprintValues {
platform?: string;
platform_version?: string;
brand?: string;
brand_version?: string;
hardware_concurrency?: number;
lang?: string;
accept_lang?: string;
timezone?: string;
disable_spoofing?: string[];
}
interface FormValues {
name: string;
platform: string;
platform_account_key: string;
tags?: string[];
cookies?: string;
fingerprint?: FingerprintValues;
}
const spoofingOptions = [
{ value: 'font', label: '字体' },
{ value: 'audio', label: '音频' },
{ value: 'canvas', label: 'Canvas' },
{ value: 'clientrects', label: 'ClientRects' },
{ value: 'gpu', label: 'GPU' },
];
export default function Page() {
const [form] = Form.useForm<FormValues>();
const [busy, setBusy] = useState(false);
@@ -31,6 +53,21 @@ export default function Page() {
};
// cookies 非必填:留空代表创建后走扫码登录,凭据由后续同步链路补齐
if (values.cookies?.trim()) data.cookies = values.cookies.trim();
// 指纹表单:只提交非空项;留空项由后端使用浏览器默认值
const fp = values.fingerprint;
if (fp) {
const fingerprint: Record<string, unknown> = {};
if (fp.platform) fingerprint.platform = fp.platform;
if (fp.platform_version?.trim()) fingerprint.platform_version = fp.platform_version.trim();
if (fp.brand) fingerprint.brand = fp.brand;
if (fp.brand_version?.trim()) fingerprint.brand_version = fp.brand_version.trim();
if (fp.hardware_concurrency) fingerprint.hardware_concurrency = fp.hardware_concurrency;
if (fp.lang?.trim()) fingerprint.lang = fp.lang.trim();
if (fp.accept_lang?.trim()) fingerprint.accept_lang = fp.accept_lang.trim();
if (fp.timezone?.trim()) fingerprint.timezone = fp.timezone.trim();
if (fp.disable_spoofing?.length) fingerprint.disable_spoofing = fp.disable_spoofing.join(',');
if (Object.keys(fingerprint).length) data.fingerprint = fingerprint;
}
const result = await create('accounts', data);
const id = result?.data?.id ?? result?.id;
if (!id) throw new Error('创建账号未返回账号 ID');
@@ -67,11 +104,95 @@ export default function Page() {
>
<Input.TextArea maxLength={8192} rows={3} placeholder="可选,如:sessionid=value; token=value" />
</Form.Item>
<Collapse
style={{ marginBottom: 24 }}
items={[
{
key: 'fingerprint',
label: '指纹浏览器环境(可选)',
children: (
<>
<Typography.Text type="secondary" style={{ display: 'block', marginBottom: 16 }}>
留空项使用浏览器默认值;seed 由系统按账号自动派生并保证唯一,代理由网络出口统一管理,均不在此配置。
</Typography.Text>
<Row gutter={16}>
<Col span={12}>
<Form.Item name={['fingerprint', 'platform']} label="平台伪装" extra="浏览器上报的操作系统">
<Select
allowClear
placeholder="默认"
options={[
{ value: 'windows', label: 'Windows' },
{ value: 'linux', label: 'Linux' },
{ value: 'macos', label: 'macOS' },
]}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name={['fingerprint', 'brand']} label="浏览器品牌" extra="UA 中的浏览器品牌">
<Select
allowClear
placeholder="默认"
options={[
{ value: 'Chrome', label: 'Chrome' },
{ value: 'Edge', label: 'Edge' },
{ value: 'Opera', label: 'Opera' },
{ value: 'Vivaldi', label: 'Vivaldi' },
]}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name={['fingerprint', 'platform_version']} label="平台版本" extra="如 10.0.0">
<Input maxLength={32} placeholder="默认" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name={['fingerprint', 'brand_version']} label="品牌版本" extra="如 132.0.6834.159">
<Input maxLength={32} placeholder="默认" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name={['fingerprint', 'lang']} label="语言" extra="如 zh-CN">
<Input maxLength={16} placeholder="默认" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name={['fingerprint', 'accept_lang']} label="接受语言" extra="逗号分隔,如 zh-CN,en-US">
<Input maxLength={256} placeholder="默认" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name={['fingerprint', 'timezone']} label="时区" extra="IANA 时区,如 Asia/Shanghai">
<Input maxLength={64} placeholder="默认" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name={['fingerprint', 'hardware_concurrency']} label="硬件并发数" extra="CPU 逻辑核数,1-128">
<InputNumber min={1} max={128} precision={0} style={{ width: '100%' }} placeholder="默认" />
</Form.Item>
</Col>
</Row>
<Form.Item
name={['fingerprint', 'disable_spoofing']}
label="关闭伪装维度"
extra="关闭指定维度的指纹伪装,保持浏览器真实特征"
>
<Select mode="multiple" allowClear placeholder="默认全部伪装" options={spoofingOptions} />
</Form.Item>
</>
),
},
]}
/>
<Form.Item>
<Button onClick={() => history.push('/accounts')}>取消</Button>
<Button type="primary" htmlType="submit" loading={busy} style={{ marginLeft: 8 }}>
{busy ? '创建中…' : '创建账号'}
</Button>
<Flex gap={8}>
<Button onClick={() => history.push('/accounts')}>取消</Button>
<Button type="primary" htmlType="submit" loading={busy}>
{busy ? '创建中…' : '创建账号'}
</Button>
</Flex>
</Form.Item>
</Form>
</Card>