feat: 风险分定时衰减 + 密码提示修复 + 浏览器池API + next回跳

1. 风险分衰减定时任务:
   - services/scheduler.py: 每天 CST 04:00 自动执行 decay_scores()
   - 支持 RISK_SCORE_DECAY_TIME_CST 环境变量覆盖

2. 密码长度提示统一为8位:
   - app-frontend/src/pages/RegisterPage.vue
   - app-frontend/src/layouts/AppLayout.vue
   - admin-frontend/src/pages/SettingsPage.vue
   - templates/register.html

3. 浏览器池统计API:
   - GET /yuyx/api/browser_pool/stats
   - 返回 worker 状态、队列等待数等信息
   - browser_pool_worker.py: 增强 get_stats() 方法

4. 登录后支持 next 参数回跳:
   - app-frontend/src/pages/LoginPage.vue: 检查 ?next= 参数
   - 仅允许站内路径(防止开放重定向)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-27 18:28:21 +08:00
parent 3d9dba272e
commit 1b20478a08
49 changed files with 305 additions and 160 deletions

View File

@@ -15,6 +15,7 @@ import {
updateEmailNotify,
} from '../api/settings'
import { useUserStore } from '../stores/user'
import { validateStrongPassword } from '../utils/password'
const route = useRoute()
const router = useRouter()
@@ -292,8 +293,9 @@ async function onChangePassword() {
ElMessage.error('请填写完整信息')
return
}
if (String(newPassword).length < 6) {
ElMessage.error('新密码至少6位')
const passwordCheck = validateStrongPassword(newPassword)
if (!passwordCheck.ok) {
ElMessage.error(passwordCheck.message)
return
}
if (newPassword !== confirmPassword) {
@@ -562,7 +564,7 @@ async function dismissAnnouncementPermanently() {
<el-form-item label="当前密码">
<el-input v-model="passwordForm.current_password" type="password" show-password autocomplete="current-password" />
</el-form-item>
<el-form-item label="新密码(至少6位">
<el-form-item label="新密码(至少8位且包含字母和数字">
<el-input v-model="passwordForm.new_password" type="password" show-password autocomplete="new-password" />
</el-form-item>
<el-form-item label="确认新密码">

View File

@@ -105,8 +105,14 @@ async function onSubmit() {
need_captcha: needCaptcha.value,
})
ElMessage.success('登录成功,正在跳转...')
const urlParams = new URLSearchParams(window.location.search || '')
const next = String(urlParams.get('next') || '').trim()
const safeNext = next && next.startsWith('/') && !next.startsWith('//') && !next.startsWith('/\\') ? next : ''
setTimeout(() => {
window.location.href = '/app'
const target = safeNext || '/app'
router.push(target).catch(() => {
window.location.href = target
})
}, 300)
} catch (e) {
const status = e?.response?.status

View File

@@ -4,6 +4,7 @@ import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { fetchEmailVerifyStatus, generateCaptcha, register } from '../api/auth'
import { validateStrongPassword } from '../utils/password'
const router = useRouter()
@@ -68,8 +69,9 @@ async function onSubmit() {
ElMessage.error(errorText.value)
return
}
if (password.length < 6) {
errorText.value = '密码至少6个字符'
const passwordCheck = validateStrongPassword(password)
if (!passwordCheck.ok) {
errorText.value = passwordCheck.message || '密码格式不正确'
ElMessage.error(errorText.value)
return
}
@@ -166,10 +168,10 @@ onMounted(async () => {
v-model="form.password"
type="password"
show-password
placeholder="至少6个字符"
placeholder="至少8位且包含字母和数字"
autocomplete="new-password"
/>
<div class="hint app-muted">至少6个字符</div>
<div class="hint app-muted">至少8位且包含字母和数字</div>
</el-form-item>
<el-form-item label="确认密码 *">
<el-input