diff --git a/backend/app/api/users.py b/backend/app/api/users.py index 623a085..6b72707 100644 --- a/backend/app/api/users.py +++ b/backend/app/api/users.py @@ -5,6 +5,7 @@ from fastapi import APIRouter, HTTPException, Request, Depends from pydantic import BaseModel from typing import List, Optional from app.user_manager import user_manager, User +from app.user_password import password_manager router = APIRouter(prefix="/users", tags=["用户管理"]) @@ -29,6 +30,11 @@ class SetAdminRequest(BaseModel): is_admin: bool +class ResetPasswordRequest(BaseModel): + user_id: str + new_password: Optional[str] = None # 如果为空则使用默认密码 + + @router.get("/current") async def get_current_user(user: User = Depends(require_login)): """获取当前登录用户信息""" @@ -122,4 +128,62 @@ async def get_user( if not user: raise HTTPException(status_code=404, detail="用户不存在") - return user.dict() \ No newline at end of file + return user.dict() + + +@router.post("/reset-password") +async def reset_user_password( + data: ResetPasswordRequest, + admin_user: User = Depends(require_admin) +): + """ + 重置用户密码(仅管理员) + + 如果提供了 new_password,则设置为指定密码 + 如果未提供 new_password,则重置为默认密码(username@666) + + 限制: + - 不能重置自己的密码(应该使用修改密码功能) + """ + # 检查是否尝试重置自己的密码 + if data.user_id == admin_user.user_id: + raise HTTPException( + status_code=400, + detail="不能重置自己的密码,请使用修改密码功能" + ) + + # 检查目标用户是否存在 + target_user = await user_manager.get_user(data.user_id) + if not target_user: + raise HTTPException( + status_code=404, + detail="目标用户不存在" + ) + + # 重置密码 + try: + actual_password = await password_manager.set_password( + target_user.user_id, + target_user.username, + data.new_password + ) + + # 如果使用了默认密码,返回密码供管理员告知用户 + message = "密码重置成功" + response_data = { + "message": message, + "user_id": data.user_id, + "username": target_user.username + } + + if not data.new_password: + response_data["default_password"] = actual_password + response_data["message"] = f"密码已重置为默认密码: {actual_password}" + + return response_data + + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"重置密码失败: {str(e)}" + ) \ No newline at end of file diff --git a/backend/app/main.py b/backend/app/main.py index 589b685..8c97c05 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -27,7 +27,23 @@ logger = get_logger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): """应用生命周期管理""" - logger.info("应用启动,等待用户登录...") + logger.info("应用启动,初始化数据库表结构...") + + # 在应用启动时初始化数据库表结构 + try: + from app.database import get_engine, Base + + # 使用全局引擎创建所有表 + engine = await get_engine("_global_init_") + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + logger.info("✅ 数据库表结构初始化成功") + except Exception as e: + logger.error(f"❌ 数据库表结构初始化失败: {str(e)}", exc_info=True) + # 不阻止应用启动,允许在后续操作中重试 + + logger.info("应用启动完成,等待用户登录...") yield diff --git a/frontend/src/components/UserMenu.tsx b/frontend/src/components/UserMenu.tsx index d88e763..05891ec 100644 --- a/frontend/src/components/UserMenu.tsx +++ b/frontend/src/components/UserMenu.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { Dropdown, Avatar, Space, Typography, message, Modal, Table, Button, Tag, Popconfirm, Pagination, Form, Input } from 'antd'; -import { UserOutlined, LogoutOutlined, TeamOutlined, CrownOutlined, LockOutlined } from '@ant-design/icons'; +import { UserOutlined, LogoutOutlined, TeamOutlined, CrownOutlined, LockOutlined, KeyOutlined } from '@ant-design/icons'; import { authApi, userApi } from '../services/api'; import type { User } from '../types'; import type { MenuProps } from 'antd'; @@ -87,6 +87,55 @@ export default function UserMenu() { } }; + const handleResetPassword = async (userId: string, username: string) => { + Modal.confirm({ + title: '重置用户密码', + content: ( +
+

确定要重置用户 {username} 的密码吗?

+

密码将被重置为默认密码:{username}@666

+
+ ), + okText: '确定重置', + cancelText: '取消', + onOk: async () => { + try { + const result = await userApi.resetPassword(userId); + if (result.default_password) { + Modal.success({ + title: '密码重置成功', + content: ( +
+

用户 {result.username} 的密码已重置为:

+

+ {result.default_password} +

+

+ 请将此密码告知用户,建议用户登录后立即修改密码 +

+
+ ), + }); + } else { + message.success('密码重置成功'); + } + loadUsers(); + } catch (error: any) { + console.error('重置密码失败:', error); + message.error(error.response?.data?.detail || '重置密码失败'); + } + }, + }); + }; + const handleChangePassword = async (values: { oldPassword: string; newPassword: string }) => { try { setChangingPassword(true); @@ -186,11 +235,11 @@ export default function UserMenu() { { title: '操作', key: 'actions', - width: 200, + width: 280, render: (_: unknown, record: User) => { const isSelf = record.user_id === currentUser?.user_id; return ( - + {record.is_admin ? ( )} + handleDeleteUser(record.user_id)} diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 7b7b527..ffeba23 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -156,6 +156,14 @@ export const userApi = { deleteUser: (userId: string) => api.delete(`/users/${userId}`), getUser: (userId: string) => api.get(`/users/${userId}`), + + resetPassword: (userId: string, newPassword?: string) => + api.post('/users/reset-password', { user_id: userId, new_password: newPassword }), }; export const settingsApi = {