fix(huya): 优化账号密码导入交互并将导入类型严格限制为四段格式

- 交互优化:将账号标签选择器移至弹窗顶部,避免下拉列表向下展开时遮挡底部的'确定'/'导入'按钮
- 格式限制:严格限制仅允许'账号----密码----手机号----接码链接'四段格式导入
- 前后端校验:前端逐行校验格式并提示具体错误行号,后端限制 require_four_segments=True 兜底
This commit is contained in:
yml2213
2026-09-06 14:00:24 +08:00
parent d3005478a9
commit cbc7f7c9c5
4 changed files with 110 additions and 29 deletions
+17
View File
@@ -76,6 +76,23 @@ def test_import_password_accounts_persists_sms_url():
assert added_account.tag == "测试接码"
def test_import_password_accounts_strictly_four_segments():
"""测试账号密码导入默认严格限制仅允许四段格式。"""
mock_db = MagicMock()
mock_db.query().filter().first.return_value = None
# 两段、三段会被跳过;仅四段有效行会被导入
text = (
"user1----pass1\n"
"user2----pass2----13800138000\n"
"user3----pass3----+14504409332----http://194.163.45.144/api/get_sms?key=xxx\n"
"user4----pass4----not_a_url\n"
)
created, skipped = import_huya_password_accounts(mock_db, text, tag="四段测试")
assert created == 1
assert skipped == 3
def test_solver_strategy_with_and_without_sms_url():
"""测试在遇到 sms_auth2 时,有接码链接不抛异常,无接码链接抛出短信异常。"""
url = "https://aq.huya.com/p/safe_auth/sms_auth2.html?param=xyz&mobile=14504409332"
+10 -3
View File
@@ -693,11 +693,18 @@ def import_password_accounts(
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""导入虎牙账号密码,只入库不立即登录。"""
"""导入虎牙账号密码,只入库不立即登录(仅允许四段格式)。"""
_require_huya_perm(current, "huya:import")
count, skipped = import_huya_password_accounts(db, req.text, req.tag)
count, skipped = import_huya_password_accounts(
db, req.text, req.tag, require_four_segments=True
)
if count == 0 and skipped > 0:
raise HTTPException(
status_code=400,
detail="导入失败:当前仅允许四段格式(账号----密码----手机号----接码链接)",
)
return {
"message": f"导入/更新 {count} 个虎牙账号,跳过 {skipped} 条",
"message": f"成功导入/更新 {count} 个虎牙账号,跳过 {skipped} 条",
"success": True,
"count": count,
"skipped": skipped,
+22 -5
View File
@@ -136,8 +136,13 @@ def _looks_like_url(value: str) -> bool:
return parsed.scheme in {"http", "https"} and bool(parsed.netloc)
def split_huya_password_line(line: str) -> HuyaPasswordLine | None:
"""拆分虎牙账号密码行,兼容账号----密码----手机号----验证码链接。"""
def split_huya_password_line(
line: str, require_four_segments: bool = False
) -> HuyaPasswordLine | None:
"""拆分虎牙账号密码行,兼容账号----密码----手机号----验证码链接。
若 require_four_segments 为 True,则严格限制必须包含手机号和有效接码链接。
"""
raw = (line or "").strip()
if not raw or raw.startswith("#"):
return None
@@ -174,6 +179,10 @@ def split_huya_password_line(line: str) -> HuyaPasswordLine | None:
cookie = "----".join(extra)
if not username or not password:
return None
if require_four_segments and (
not phone or not sms_url or not _looks_like_url(sms_url)
):
return None
return HuyaPasswordLine(
username=username,
password=password,
@@ -184,15 +193,23 @@ def split_huya_password_line(line: str) -> HuyaPasswordLine | None:
def import_huya_password_accounts(
db: Session, text: str, tag: str = ""
db: Session,
text: str,
tag: str = "",
require_four_segments: bool = True,
) -> tuple[int, int]:
"""导入虎牙账号密码,返回 (导入/更新数, 跳过数)。"""
"""导入虎牙账号密码,返回 (导入/更新数, 跳过数)。
默认仅允许四段格式(账号----密码----手机号----接码链接)。
"""
created_or_updated = 0
skipped = 0
tag = (tag or "").strip()
for line in (text or "").splitlines():
parsed = split_huya_password_line(line)
parsed = split_huya_password_line(
line, require_four_segments=require_four_segments
)
if not parsed:
if line.strip():
skipped += 1
+61 -21
View File
@@ -278,10 +278,36 @@ export default function HuyaAccountsPage() {
};
const handleImportPasswordAccounts = async () => {
if (!passwordImportText.trim()) {
const rawLines = passwordImportText
.split('\n')
.map((l) => l.trim())
.filter((l) => l && !l.startsWith('#'));
if (rawLines.length === 0) {
message.warning('请先粘贴虎牙账号密码');
return;
}
const invalidLines: number[] = [];
rawLines.forEach((line, idx) => {
const parts = line.includes('----')
? line.split('----').map((p) => p.trim())
: line.split(/\s+/).map((p) => p.trim());
if (
parts.length < 4 ||
!parts[0] ||
!parts[1] ||
!parts[2] ||
!parts[3] ||
!/^https?:\/\//i.test(parts[3])
) {
invalidLines.push(idx + 1);
}
});
if (invalidLines.length > 0) {
message.error(
`格式错误:仅允许四段格式(账号----密码----手机号----接码链接)。第 ${invalidLines.slice(0, 5).join('、')}${invalidLines.length > 5 ? ` 等共 ${invalidLines.length}` : ''} 行不符合要求`
);
return;
}
setPasswordImporting(true);
try {
const tag = passwordImportTag.length > 0 ? passwordImportTag[passwordImportTag.length - 1].trim() : '';
@@ -946,27 +972,41 @@ export default function HuyaAccountsPage() {
closable={!passwordImporting}
width={720}
>
<Space orientation="vertical" style={{ width: '100%' }} size={12}>
<TextArea
rows={10}
value={passwordImportText}
onChange={(e) => setPasswordImportText(e.target.value)}
placeholder={`每行一条格式:\n1. 账号----密码\n2. 账号----密码----手机号----接码链接\n例如:hy_299112483----aa778899----+14504409332----http://194.163.45.144/api/get_sms?key=c16fe33219924363fa362ed69dc6fd4f`}
disabled={passwordImporting}
<Space orientation="vertical" style={{ width: '100%' }} size={14}>
<Alert
type="info"
showIcon
message="仅支持四段格式导入:账号----密码----手机号----接码链接"
description="手机号与接码链接将同步保存入库。后续 App 密码登录如触发短信二次验证(sms_auth2),系统将自动从接码链接提取验证码完成全自动无人值守过验。"
/>
<Select
mode="tags"
style={{ width: '100%' }}
placeholder="可选,保存到账号标签"
maxCount={1}
value={passwordImportTag}
onChange={setPasswordImportTag}
options={tags.map((tag) => ({ value: tag, label: tag }))}
disabled={passwordImporting}
/>
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
四段格式:<code>账号----密码----手机号----接码链接</code>。接码链接与手机号将自动保存入库。后续 App 密码登录如触发短信二次验证(sms_auth2),系统将自动从接码链接获取验证码完成全自动过验!
</Paragraph>
<div>
<div style={{ marginBottom: 6, fontWeight: 500 }}>
账号标签 <span style={{ color: '#8c8c8c', fontWeight: 'normal', fontSize: 12 }}>(可选,批量设置账号标签)</span>
</div>
<Select
mode="tags"
style={{ width: '100%' }}
placeholder="输入或选择账号标签"
maxCount={1}
value={passwordImportTag}
onChange={setPasswordImportTag}
options={tags.map((tag) => ({ value: tag, label: tag }))}
disabled={passwordImporting}
placement="bottomLeft"
/>
</div>
<div>
<div style={{ marginBottom: 6, fontWeight: 500 }}>
账号数据 <span style={{ color: '#ff4d4f', fontSize: 12 }}>* 仅允许四段格式(账号----密码----手机号----接码链接)</span>
</div>
<TextArea
rows={9}
value={passwordImportText}
onChange={(e) => setPasswordImportText(e.target.value)}
placeholder={`每行一条四段格式:\n账号----密码----手机号----接码链接\n\n例如:\nhy_299112483----aa778899----+14504409332----http://194.163.45.144/api/get_sms?key=c16fe33219924363fa362ed69dc6fd4f`}
disabled={passwordImporting}
/>
</div>
</Space>
</Modal>