feat: Provider 异步删除、可配置密码策略、Hub 超时优化及多项改进

- 新增 Provider 异步删除任务系统,后台分阶段删除子资源并清理残留引用
- 新增可配置密码策略等级(weak/medium/strong),支持系统设置面板调整
- aether-hub 升级至 0.1.4,idle timeout 支持禁用(设为 0),worker 默认超时调整为 120s
- OAuth 手动续期增加 Redis 分布式锁,防止并发刷新冲突
- ProxyNode 心跳检测改为 asyncio.to_thread,避免阻塞事件循环
- 删除 ModelMultiSelect 和 useInvalidModels,MultiSelect 组件通用化
- 明确 allowed_providers/allowed_api_formats 的 NULL 与空数组语义
- 前端 StandaloneKeyFormDialog、UserFormDialog 等多处 UI 优化
- 新增 Alembic 迁移脚本清理 Provider 删除后的残留引用
- 补充相关测试用例
This commit is contained in:
fawney19
2026-03-12 01:11:35 +08:00
parent 0d770d1c4d
commit 6e51a3f45d
55 changed files with 3219 additions and 862 deletions

View File

@@ -119,6 +119,18 @@
type="password"
class="mt-1"
/>
<p
v-if="passwordError"
class="mt-1 text-xs text-destructive"
>
{{ passwordError }}
</p>
<p
v-else
class="mt-1 text-xs text-muted-foreground"
>
{{ passwordPolicyHint }}
</p>
</div>
<div>
<Label for="confirm-password">确认{{ profile?.has_password ? '新' : '' }}密码</Label>
@@ -465,6 +477,12 @@ import { authApi } from '@/api/auth'
import { oauthApi, type OAuthLinkInfo, type OAuthProviderInfo } from '@/api/oauth'
import { getOAuthIcon } from '@/utils/oauth-icons'
import { useDarkMode, type ThemeMode } from '@/composables/useDarkMode'
import {
getPasswordPolicyHint,
normalizePasswordPolicyLevel,
validatePasswordByPolicy,
type PasswordPolicyLevel,
} from '@/utils/passwordPolicy'
import Card from '@/components/ui/card.vue'
import Button from '@/components/ui/button.vue'
import Badge from '@/components/ui/badge.vue'
@@ -516,6 +534,7 @@ const preferencesForm = ref({
const savingProfile = ref(false)
const changingPassword = ref(false)
const passwordPolicyLevel = ref<PasswordPolicyLevel>('weak')
const themeSelectOpen = ref(false)
const languageSelectOpen = ref(false)
@@ -539,6 +558,11 @@ const hasProfileChanges = computed(() => {
)
})
const passwordPolicyHint = computed(() => getPasswordPolicyHint(passwordPolicyLevel.value))
const passwordError = computed(() =>
validatePasswordByPolicy(passwordForm.value.new_password, passwordPolicyLevel.value)
)
// 检测密码表单是否有内容
const hasPasswordChanges = computed(() => {
const hasPassword = profile.value?.has_password
@@ -577,8 +601,10 @@ async function loadEmailConfigured() {
try {
const settings = await authApi.getRegistrationSettings()
emailConfigured.value = !!settings.email_configured
passwordPolicyLevel.value = normalizePasswordPolicyLevel(settings.password_policy_level)
} catch {
emailConfigured.value = false
passwordPolicyLevel.value = 'weak'
}
}
@@ -766,8 +792,8 @@ async function changePassword() {
return
}
if (passwordForm.value.new_password.length < 6) {
showError('密码长度至少6位', '密码错误')
if (passwordError.value) {
showError(passwordError.value, '密码错误')
return
}