# api/users/router.py
from fastapi import APIRouter, Request, Depends, Query
from fastapi.responses import JSONResponse
from datetime import timedelta
import logging
from backend.schemas import UserCreate, UserUpdate
from backend.jwt_handler import (
ACCESS_TOKEN_EXPIRE_MINUTES,
TokenData,
get_admin_user,
get_current_user_token_data,
verify_password,
get_password_hash,
create_access_token
)
from backend.database import get_db
from sqlalchemy.ext.asyncio import AsyncSession
from backend.repositories.user_repo import UserRepository
router = APIRouter(prefix="/api/users", tags=["用户管理"])
logger = logging.getLogger(__name__)
@router.post("/login", tags=["用户认证"])
async def login(request: Request, db: AsyncSession = Depends(get_db)):
data = await request.json()
account = data.get("account")
password = data.get("password")
if not account or not password:
return JSONResponse({"success": False, "message": "请输入用户名和密码"}, status_code=400)
user_repo = UserRepository(db)
user = await user_repo.get_user_by_account(account)
if not user:
return JSONResponse({"success": False, "message": "用户名或密码错误"}, status_code=401)
if not verify_password(password, user["password"]):
return JSONResponse({"success": False, "message": "用户名或密码错误"}, status_code=401)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={
"sub": str(user["id"]),
"role": user["role"],
"dept_id": user["department_id"]
},
expires_delta=access_token_expires
)
response = JSONResponse({"success": True, "account": account})
response.set_cookie(
key="access_token",
value=access_token,
httponly=True,
secure=False,
samesite="lax",
max_age=access_token_expires.total_seconds()
)
logger.info(f"🔐 用户 {user['id']} 登录成功")
return response
@router.post("/register")
async def user_register(
user_data: UserCreate,
db: AsyncSession = Depends(get_db)
):
repo = UserRepository(db)
existing = await repo.get_user_by_account(user_data.account)
if existing:
return JSONResponse({"success": False, "message": "用户名已存在"}, status_code=400)
hashed_pwd = get_password_hash(user_data.password)
new_user = await repo.create_user(
account=user_data.account,
password=hashed_pwd,
role="user",
department_id=user_data.department_id
)
await db.commit()
logger.info(f"🆕 用户 {new_user['id']} 注册成功")
return JSONResponse(
{"success": True, "data": {"user_id": new_user["id"], "account": new_user["account"]}},
status_code=201
)
@router.get("/me")
async def get_current_user_info(
current_user: TokenData = Depends(get_current_user_token_data),
db: AsyncSession = Depends(get_db)
):
repo = UserRepository(db)
detail = await repo.get_user_detail(current_user.user_id)
if not detail:
return JSONResponse({"success": False, "message": "用户不存在"}, status_code=404)
return JSONResponse({
"success": True,
"data": {
"user_id": detail["id"],
"account": detail["account"],
"role": detail["role"],
"department": {
"id": detail["department_id"],
"name": detail["dept_name"]
},
"created_at": detail["created_at"].strftime("%Y-%m-%d %H:%M:%S")
}
})
@router.put("/me")
async def update_current_user(
update_data: UserUpdate,
current_user: TokenData = Depends(get_current_user_token_data),
db: AsyncSession = Depends(get_db)
):
repo = UserRepository(db)
updates = {}
if update_data.password:
updates["password"] = get_password_hash(update_data.password)
if update_data.department_id:
if not await repo.check_department_exists(update_data.department_id):
return JSONResponse({"success": False, "message": "院系不存在"}, status_code=404)
updates["department_id"] = update_data.department_id
if updates:
await repo.update_user(current_user.user_id, updates)
await db.commit()
logger.info(f"🔄 用户 {current_user.user_id} 更新信息")
return JSONResponse({"success": True, "message": "更新成功"})
@router.get("/admin/list")
async def get_admin_user_list(
page: int = Query(1, ge=1),
size: int = Query(10, ge=1, le=50),
role: str = Query(None, pattern="^(admin|user|dept_admin)$"),
dept_id: int = Query(None),
admin: TokenData = Depends(get_admin_user),
db: AsyncSession = Depends(get_db)
):
repo = UserRepository(db)
skip = (page - 1) * size
total, users = await repo.get_users_list(role=role, dept_id=dept_id, skip=skip, limit=size)
return JSONResponse({
"success": True,
"data": {
"total": total,
"page": page,
"size": size,
"items": [
{
"user_id": u["id"],
"account": u["account"],
"role": u["role"],
"department_id": u["department_id"],
"created_at": u["created_at"].strftime("%Y-%m-%d %H:%M:%S")
} for u in users
]
}
})
@router.put("/admin/{user_id}/role")
async def update_user_role(
user_id: int,
new_role: str = Query(..., pattern="^(admin|user|dept_admin)$"),
admin: TokenData = Depends(get_admin_user),
db: AsyncSession = Depends(get_db)
):
repo = UserRepository(db)
user = await repo.get_user_by_id(user_id)
if not user:
return JSONResponse({"success": False, "message": "用户不存在"}, status_code=404)
await repo.update_user(user_id, {"role": new_role})
await db.commit()
logger.info(f"🔧 管理员 {admin.user_id} 修改用户 {user_id} 角色为 {new_role}")
return JSONResponse({"success": True, "message": f"角色已更新为 {new_role}"})
# /backend/routes/page_routes.py
"""
前端页面路由模块(HTML 渲染)
职责:仅用于返回 HTML 页面,不处理 API 逻辑
依赖:Jinja2Templates + 静态资源挂载在 main.py 中完成
"""
from fastapi import APIRouter, Request, Depends, HTTPException
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
import logging
from typing import Dict, Any
from backend.jwt_handler import get_current_user_token_data, get_admin_user
from backend.database import get_db
from sqlalchemy.ext.asyncio import AsyncSession
from backend.repositories.character_repo import CharacterRepository
# 初始化日志
logger = logging.getLogger(__name__)
# 路径配置
BASE_DIR = Path(__file__).resolve().parent.parent.parent # 项目根目录
TEMPLATES_DIR = BASE_DIR / "frontend" / "templates"
# 初始化模板引擎
try:
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
logger.info(f"✅ Jinja2 模板引擎已加载:{TEMPLATES_DIR}")
except Exception as e:
logger.error(f"❌ 初始化模板引擎失败:{str(e)}")
raise
# 创建路由器
router = APIRouter(tags=["前端页面"])
# -----------------------
# 工具函数:安全渲染模板
# -----------------------
def render_template_or_error(
template_name: str,
context: Dict[str, Any],
request: Request
) -> HTMLResponse:
"""封装模板渲染,捕获异常"""
try:
return templates.TemplateResponse(template_name, {**context, "request": request})
except Exception as e:
logger.error(f"❌ 渲染模板失败 | 模板:{template_name} | 错误:{str(e)}")
raise HTTPException(status_code=500, detail="页面渲染错误")
# -----------------------
# 前端页面路由定义
# -----------------------
@router.get("/", response_class=HTMLResponse)
async def home(request: Request):
"""首页 - 无需登录"""
logger.info("📱 访问首页")
return render_template_or_error("index.html", {}, request)
@router.get("/login", response_class=HTMLResponse)
async def login_page(request: Request):
"""登录页 - 无需认证"""
logger.info("🔑 访问登录页")
return render_template_or_error("login.html", {}, request)
@router.get("/chat", response_class=HTMLResponse)
async def chat_page(
request: Request,
token_data=Depends(get_current_user_token_data),
db: AsyncSession = Depends(get_db)
):
"""
聊天页面 - 需登录
加载 AI 角色列表供前端选择
"""
current_user_id = token_data.user_id
client_ip = request.client.host
logger.info(f"📋 用户访问聊天页 | ID:{current_user_id} | 角色:{token_data.role} | IP:{client_ip}")
repo = CharacterRepository(db)
try:
characters = await repo.get_all()
chars_list = [
{"id": c.id, "name": c.name, "trait": c.trait}
for c in characters
]
characters_json = f"<script>window.CHARACTERS = {repr(chars_list)};</script>"
except Exception as e:
logger.error(f"❌ 加载AI角色失败:{str(e)}", exc_info=True)
raise HTTPException(status_code=500, detail="无法加载AI角色数据")
context = {
"characters": chars_list,
"characters_json": characters_json,
"user_role": token_data.role,
"user_dept": token_data.department_id,
"debug_user": current_user_id
}
return render_template_or_error("chat.html", context, request)
@router.get("/share", response_class=HTMLResponse)
async def share_page(
request: Request,
token_data=Depends(get_current_user_token_data)
):
"""分享广场页面 - 需登录"""
logger.info(f"📌 用户访问分享页 | ID:{token_data.user_id}")
return render_template_or_error("share.html", {}, request)
@router.get("/search", response_class=HTMLResponse)
async def search_page(
request: Request,
token_data=Depends(get_current_user_token_data)
):
"""搜索推荐页面 - 需登录"""
logger.info(f"🔍 用户访问搜索页 | ID:{token_data.user_id}")
return render_template_or_error("search.html", {}, request)
@router.get("/admin", response_class=HTMLResponse)
async def admin_page(
request: Request,
admin=Depends(get_admin_user) # 仅管理员可访问
):
"""管理员后台首页"""
logger.info(f"🛠️ 管理员访问后台 | ID:{admin.user_id}")
return render_template_or_error("admin/index.html", {}, request)
有一些网页页面没有配置,补全并设计,给我全代码
最新发布