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 from datetime import datetime
import re
from fastapi import APIRouter, Depends, HTTPException, Request, status, UploadFile, File from fastapi import APIRouter, Depends, HTTPException, Request, status, UploadFile, File
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
@ -60,8 +61,9 @@ class UserResponse(BaseModel):
class InitializeAdmin(BaseModel): class InitializeAdmin(BaseModel):
username: str user_id: str # 直接输入用户ID
password: str password: str
phone_number: str | None = None
class UsernameValidation(BaseModel): 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) hashed_password = AuthUtils.hash_password(admin_data.password)
# 验证用户名 # 验证用户ID格式只支持字母数字和下划线
is_valid, error_msg = validate_username(admin_data.username) if not re.match(r'^[a-zA-Z0-9_]+$', admin_data.user_id):
if not is_valid:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail=error_msg, detail="用户ID只能包含字母、数字和下划线",
) )
# 生成user_id if len(admin_data.user_id) < 3 or len(admin_data.user_id) > 20:
existing_user_ids = [] raise HTTPException(
user_id = generate_unique_user_id(admin_data.username, existing_user_ids) 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( new_admin = User(
username=admin_data.username, username=admin_data.user_id, # username和user_id设置为相同值
user_id=user_id, user_id=user_id,
phone_number=admin_data.phone_number,
avatar=None, # 初始化时头像为空
password_hash=hashed_password, password_hash=hashed_password,
role="superadmin", role="superadmin",
last_login=datetime.now() last_login=datetime.now()

View File

@ -222,7 +222,7 @@ const currentAgentId = computed(() => {
}); });
const currentAgentMetadata = 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 agentStore?.metadata[currentAgentId.value]
} }
return {} return {}

View File

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

View File

@ -39,8 +39,12 @@
<!-- 初始化管理员表单 --> <!-- 初始化管理员表单 -->
<div v-if="isFirstRun" class="login-form"> <div v-if="isFirstRun" class="login-form">
<h2>系统初始化</h2> <h2>系统初始化请创建超级管理员账户</h2>
<p class="init-desc">系统首次运行请创建超级管理员账户</p> <div class="init-tips">
<p> 请输入用户ID仅支持字母数字和下划线</p>
<p> 手机号可选填写用于后续登录</p>
<p> 请妥善保管管理员账户信息</p>
</div>
<a-form <a-form
:model="adminForm" :model="adminForm"
@ -48,11 +52,50 @@
layout="vertical" layout="vertical"
> >
<a-form-item <a-form-item
label="用户名" label="用户ID"
name="username" name="user_id"
:rules="[{ required: true, message: '请输入用户名' }]" :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>
<a-form-item <a-form-item
@ -175,7 +218,7 @@
</template> </template>
<script setup> <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 { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user'; import { useUserStore } from '@/stores/user';
import { useInfoStore } from '@/stores/info'; import { useInfoStore } from '@/stores/info';
@ -215,9 +258,10 @@ const loginForm = reactive({
// //
const adminForm = reactive({ const adminForm = reactive({
username: '', user_id: '', // user_id
password: '', password: '',
confirmPassword: '' confirmPassword: '',
phone_number: '' //
}); });
// //
@ -269,14 +313,13 @@ const formatTime = (seconds) => {
}; };
// //
const validateConfirmPassword = (rule, value) => { const validateConfirmPassword = async (rule, value) => {
if (value === '') { if (value === '') {
return Promise.reject('请确认密码'); throw new Error('请确认密码');
} }
if (value !== adminForm.password) { if (value !== adminForm.password) {
return Promise.reject('两次输入的密码不一致'); throw new Error('两次输入的密码不一致');
} }
return Promise.resolve();
}; };
// //
@ -388,8 +431,9 @@ const handleInitialize = async () => {
} }
await userStore.initialize({ await userStore.initialize({
username: adminForm.username, user_id: adminForm.user_id,
password: adminForm.password password: adminForm.password,
phone_number: adminForm.phone_number || null // null
}); });
message.success('管理员账户创建成功'); message.success('管理员账户创建成功');
@ -624,10 +668,28 @@ onUnmounted(() => {
} }
} }
.init-desc { .init-tips {
margin-bottom: 24px; background: #f6ffed;
color: #666; border: 1px solid #b7eb8f;
text-align: center; 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 { .error-message {