feat(auth): 支持管理员初始化时使用用户ID和手机号

This commit is contained in:
Wenjie Zhang 2025-09-21 14:31:10 +08:00
parent 6a2bf4f0f2
commit c934ccb10d
4 changed files with 108 additions and 33 deletions

View File

@ -1,4 +1,5 @@
from datetime import datetime
import re
from fastapi import APIRouter, Depends, HTTPException, Request, status, UploadFile, File
from fastapi.security import OAuth2PasswordRequestForm
@ -60,8 +61,9 @@ class UserResponse(BaseModel):
class InitializeAdmin(BaseModel):
username: str
user_id: str # 直接输入用户ID
password: str
phone_number: str | None = None
class UsernameValidation(BaseModel):
@ -182,21 +184,34 @@ async def initialize_admin(admin_data: InitializeAdmin, db: Session = Depends(ge
# 创建管理员账户
hashed_password = AuthUtils.hash_password(admin_data.password)
# 验证用户名
is_valid, error_msg = validate_username(admin_data.username)
if not is_valid:
# 验证用户ID格式只支持字母数字和下划线
if not re.match(r'^[a-zA-Z0-9_]+$', admin_data.user_id):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=error_msg,
detail="用户ID只能包含字母、数字和下划线",
)
# 生成user_id
existing_user_ids = []
user_id = generate_unique_user_id(admin_data.username, existing_user_ids)
if len(admin_data.user_id) < 3 or len(admin_data.user_id) > 20:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="用户ID长度必须在3-20个字符之间",
)
# 验证手机号格式(如果提供了)
if admin_data.phone_number and not is_valid_phone_number(admin_data.phone_number):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="手机号格式不正确"
)
# 由于是首次初始化直接使用输入的user_id
user_id = admin_data.user_id
new_admin = User(
username=admin_data.username,
username=admin_data.user_id, # username和user_id设置为相同值
user_id=user_id,
phone_number=admin_data.phone_number,
avatar=None, # 初始化时头像为空
password_hash=hashed_password,
role="superadmin",
last_login=datetime.now()

View File

@ -222,7 +222,7 @@ const currentAgentId = computed(() => {
});
const currentAgentMetadata = computed(() => {
if (agentStore?.metadata && CurrentAgentId.value in agentStore?.metadata[currentAgentId.value]) {
if (agentStore?.metadata && currentAgentId.value in agentStore?.metadata[currentAgentId.value]) {
return agentStore?.metadata[currentAgentId.value]
}
return {}

View File

@ -176,7 +176,7 @@
<script setup>
import { reactive, onMounted, watch } from 'vue';
import { notification } from 'ant-design-vue';
import { notification, Modal } from 'ant-design-vue';
import { useUserStore } from '@/stores/user';
import {
DeleteOutlined,
@ -423,9 +423,7 @@ const confirmDeleteUser = (user) => {
}
//
const { modal } = notification;
modal.confirm({
Modal.confirm({
title: '确认删除用户',
content: `确定要删除用户 "${user.username}" 吗?此操作不可撤销。`,
okText: '删除',

View File

@ -39,8 +39,12 @@
<!-- 初始化管理员表单 -->
<div v-if="isFirstRun" class="login-form">
<h2>系统初始化</h2>
<p class="init-desc">系统首次运行请创建超级管理员账户</p>
<h2>系统初始化请创建超级管理员账户</h2>
<div class="init-tips">
<p> 请输入用户ID仅支持字母数字和下划线</p>
<p> 手机号可选填写用于后续登录</p>
<p> 请妥善保管管理员账户信息</p>
</div>
<a-form
:model="adminForm"
@ -48,11 +52,50 @@
layout="vertical"
>
<a-form-item
label="用户名"
name="username"
:rules="[{ required: true, message: '请输入用户名' }]"
label="用户ID"
name="user_id"
:rules="[
{ required: true, message: '请输入用户ID' },
{
pattern: /^[a-zA-Z0-9_]+$/,
message: '用户ID只能包含字母、数字和下划线'
},
{
min: 3,
max: 20,
message: '用户ID长度必须在3-20个字符之间'
}
]"
>
<a-input v-model:value="adminForm.username" prefix-icon="user" />
<a-input
v-model:value="adminForm.user_id"
placeholder="请输入用户ID3-20个字符"
:maxlength="20"
/>
</a-form-item>
<a-form-item
label="手机号(可选)"
name="phone_number"
:rules="[
{
validator: async (rule, value) => {
if (!value || value.trim() === '') {
return; //
}
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(value)) {
throw new Error('请输入正确的手机号格式');
}
}
}
]"
>
<a-input
v-model:value="adminForm.phone_number"
placeholder="可用于登录,可不填写"
:max-length="11"
/>
</a-form-item>
<a-form-item
@ -175,7 +218,7 @@
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted, computed } from 'vue';
import { ref, reactive, onMounted, onUnmounted, computed, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user';
import { useInfoStore } from '@/stores/info';
@ -215,9 +258,10 @@ const loginForm = reactive({
//
const adminForm = reactive({
username: '',
user_id: '', // user_id
password: '',
confirmPassword: ''
confirmPassword: '',
phone_number: '' //
});
//
@ -269,14 +313,13 @@ const formatTime = (seconds) => {
};
//
const validateConfirmPassword = (rule, value) => {
const validateConfirmPassword = async (rule, value) => {
if (value === '') {
return Promise.reject('请确认密码');
throw new Error('请确认密码');
}
if (value !== adminForm.password) {
return Promise.reject('两次输入的密码不一致');
throw new Error('两次输入的密码不一致');
}
return Promise.resolve();
};
//
@ -388,8 +431,9 @@ const handleInitialize = async () => {
}
await userStore.initialize({
username: adminForm.username,
password: adminForm.password
user_id: adminForm.user_id,
password: adminForm.password,
phone_number: adminForm.phone_number || null // null
});
message.success('管理员账户创建成功');
@ -624,10 +668,28 @@ onUnmounted(() => {
}
}
.init-desc {
margin-bottom: 24px;
color: #666;
text-align: center;
.init-tips {
background: #f6ffed;
border: 1px solid #b7eb8f;
border-radius: 6px;
padding: 12px 16px;
margin-bottom: 20px;
text-align: left;
p {
margin: 4px 0;
font-size: 13px;
color: #52c41a;
line-height: 1.4;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
}
.error-message {