main
mula.liu 2025-09-11 13:16:58 +08:00
parent a192590c3b
commit aad59dd5df
32 changed files with 216 additions and 25 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -8,6 +8,8 @@ WORKDIR /app
ENV PYTHONPATH=/app
ENV PYTHONUNBUFFERED=1
# 使用阿里源
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
@ -19,7 +21,7 @@ RUN apt-get update && apt-get install -y \
COPY requirements-prod.txt .
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements-prod.txt
RUN pip install --index-url https://mirrors.aliyun.com/pypi/simple --no-cache-dir -r requirements-prod.txt
# 复制应用代码
COPY . .

View File

@ -19,7 +19,7 @@ def login(request: LoginRequest):
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
query = "SELECT user_id, username, caption, email, password_hash FROM users WHERE username = %s"
query = "SELECT user_id, username, caption, email, password_hash, role_id FROM users WHERE username = %s"
cursor.execute(query, (request.username,))
user = cursor.fetchone()
@ -27,14 +27,15 @@ def login(request: LoginRequest):
raise HTTPException(status_code=401, detail="用户名或密码错误")
hashed_input = hash_password(request.password)
if user['password_hash'] != hashed_input and user['password_hash'] != request.password:
if user['password_hash'] != hashed_input:
raise HTTPException(status_code=401, detail="用户名或密码错误")
# 创建JWT token
token_data = {
"user_id": user['user_id'],
"username": user['username'],
"caption": user['caption']
"caption": user['caption'],
"role_id": user['role_id']
}
token = jwt_service.create_access_token(token_data)
@ -43,7 +44,8 @@ def login(request: LoginRequest):
username=user['username'],
caption=user['caption'],
email=user['email'],
token=token
token=token,
role_id=user['role_id']
)
@router.post("/auth/logout")
@ -108,7 +110,8 @@ def refresh_token(current_user: dict = Depends(get_current_user)):
token_data = {
"user_id": current_user['user_id'],
"username": current_user['username'],
"caption": current_user['caption']
"caption": current_user['caption'],
"role_id": current_user['role_id']
}
new_token = jwt_service.create_access_token(token_data)
return {"token": new_token}

View File

@ -1,28 +1,171 @@
from fastapi import APIRouter, HTTPException, Depends
from app.models.models import UserInfo
from app.models.models import UserInfo, PasswordChangeRequest, UserListResponse, CreateUserRequest, UpdateUserRequest
from app.core.database import get_db_connection
from app.core.auth import get_current_user
from app.core.config import DEFAULT_RESET_PASSWORD
import hashlib
import datetime
import re
router = APIRouter()
@router.get("/users", response_model=list[UserInfo])
def get_all_users(current_user: dict = Depends(get_current_user)):
def validate_email(email: str) -> bool:
"""Basic email validation"""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
def hash_password(password: str) -> str:
return hashlib.sha256(password.encode()).hexdigest()
@router.post("/users", status_code=201)
def create_user(request: CreateUserRequest, current_user: dict = Depends(get_current_user)):
if current_user['role_id'] != 1: # 1 is admin
raise HTTPException(status_code=403, detail="仅管理员有权限创建用户")
# Validate email format
if not validate_email(request.email):
raise HTTPException(status_code=400, detail="邮箱格式不正确")
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# Check if username exists
cursor.execute("SELECT user_id FROM users WHERE username = %s", (request.username,))
if cursor.fetchone():
raise HTTPException(status_code=400, detail="用户名已存在")
# Use provided password or default password
password = request.password if request.password else DEFAULT_RESET_PASSWORD
hashed_password = hash_password(password)
# Insert new user
query = "INSERT INTO users (username, password_hash, caption, email, role_id, created_at) VALUES (%s, %s, %s, %s, %s, %s)"
created_at = datetime.datetime.utcnow()
cursor.execute(query, (request.username, hashed_password, request.caption, request.email, request.role_id, created_at))
connection.commit()
return {"message": "用户创建成功"}
@router.put("/users/{user_id}", response_model=UserInfo)
def update_user(user_id: int, request: UpdateUserRequest, current_user: dict = Depends(get_current_user)):
if current_user['role_id'] != 1: # 1 is admin
raise HTTPException(status_code=403, detail="仅管理员有权限修改用户信息")
# Validate email format if provided
if request.email and not validate_email(request.email):
raise HTTPException(status_code=400, detail="邮箱格式不正确")
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# Check if user exists
cursor.execute("SELECT user_id, username, caption, email, role_id FROM users WHERE user_id = %s", (user_id,))
existing_user = cursor.fetchone()
if not existing_user:
raise HTTPException(status_code=404, detail="用户不存在")
# Check if username is being changed and if it already exists
if request.username and request.username != existing_user['username']:
cursor.execute("SELECT user_id FROM users WHERE username = %s AND user_id != %s", (request.username, user_id))
if cursor.fetchone():
raise HTTPException(status_code=400, detail="用户名已存在")
# Prepare update data, using existing values if not provided
update_data = {
'username': request.username if request.username else existing_user['username'],
'caption': request.caption if request.caption else existing_user['caption'],
'email': request.email if request.email else existing_user['email'],
'role_id': request.role_id if request.role_id is not None else existing_user['role_id']
}
# Update user
query = "UPDATE users SET username = %s, caption = %s, email = %s, role_id = %s WHERE user_id = %s"
cursor.execute(query, (update_data['username'], update_data['caption'], update_data['email'], update_data['role_id'], user_id))
connection.commit()
# Return updated user info
cursor.execute("SELECT user_id, username, caption, email, created_at FROM users WHERE user_id = %s", (user_id,))
updated_user = cursor.fetchone()
return UserInfo(
user_id=updated_user['user_id'],
username=updated_user['username'],
caption=updated_user['caption'],
email=updated_user['email'],
created_at=updated_user['created_at'],
meetings_created=0, # This is not accurate, but it is not displayed in the list
meetings_attended=0
)
@router.delete("/users/{user_id}")
def delete_user(user_id: int, current_user: dict = Depends(get_current_user)):
if current_user['role_id'] != 1: # 1 is admin
raise HTTPException(status_code=403, detail="仅管理员有权限删除用户")
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# Check if user exists
cursor.execute("SELECT user_id FROM users WHERE user_id = %s", (user_id,))
if not cursor.fetchone():
raise HTTPException(status_code=404, detail="用户不存在")
# Delete user
cursor.execute("DELETE FROM users WHERE user_id = %s", (user_id,))
connection.commit()
return {"message": "用户删除成功"}
@router.post("/users/{user_id}/reset-password")
def reset_password(user_id: int, current_user: dict = Depends(get_current_user)):
if current_user['role_id'] != 1: # 1 is admin
raise HTTPException(status_code=403, detail="仅管理员有权限重置密码")
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# Check if user exists
cursor.execute("SELECT user_id FROM users WHERE user_id = %s", (user_id,))
if not cursor.fetchone():
raise HTTPException(status_code=404, detail="用户不存在")
# Hash password
hashed_password = hash_password(DEFAULT_RESET_PASSWORD)
# Update user password
query = "UPDATE users SET password_hash = %s WHERE user_id = %s"
cursor.execute(query, (hashed_password, user_id))
connection.commit()
return {"message": f"用户 {user_id} 的密码已重置"}
@router.get("/users", response_model=UserListResponse)
def get_all_users(page: int = 1, size: int = 10, current_user: dict = Depends(get_current_user)):
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# Get total count
cursor.execute("SELECT COUNT(*) as total FROM users")
total = cursor.fetchone()['total']
# Get paginated users
offset = (page - 1) * size
query = '''
SELECT
user_id, username, caption, email, created_at,
(SELECT COUNT(*) FROM meetings WHERE user_id = u.user_id) as meetings_created,
(SELECT COUNT(*) FROM attendees WHERE user_id = u.user_id) as meetings_attended
FROM users u
ORDER BY caption ASC
ORDER BY user_id ASC
LIMIT %s OFFSET %s
'''
cursor.execute(query)
cursor.execute(query, (size, offset))
users = cursor.fetchall()
return [UserInfo(**user) for user in users]
user_list = [UserInfo(**user) for user in users]
return UserListResponse(users=user_list, total=total)
@router.get("/users/{user_id}", response_model=UserInfo)
def get_user_info(user_id: int, current_user: dict = Depends(get_current_user)):
@ -53,3 +196,28 @@ def get_user_info(user_id: int, current_user: dict = Depends(get_current_user)):
meetings_created=meetings_created,
meetings_attended=meetings_attended
)
@router.put("/users/{user_id}/password")
def update_password(user_id: int, request: PasswordChangeRequest, current_user: dict = Depends(get_current_user)):
if user_id != current_user['user_id'] and current_user['role_id'] != 1:
raise HTTPException(status_code=403, detail="没有权限修改其他用户的密码")
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
cursor.execute("SELECT password_hash FROM users WHERE user_id = %s", (user_id,))
user = cursor.fetchone()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# If not admin, verify old password
if current_user['role_id'] != 1:
if user['password_hash'] != hash_password(request.old_password):
raise HTTPException(status_code=400, detail="旧密码错误")
new_password_hash = hash_password(request.new_password)
cursor.execute("UPDATE users SET password_hash = %s WHERE user_id = %s", (new_password_hash, user_id))
connection.commit()
return {"message": "密码修改成功"}

View File

@ -24,7 +24,7 @@ def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(securit
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
cursor.execute(
"SELECT user_id, username, caption, email FROM users WHERE user_id = %s",
"SELECT user_id, username, caption, email, role_id FROM users WHERE user_id = %s",
(user_id,)
)
user = cursor.fetchone()

View File

@ -20,7 +20,7 @@ MARKDOWN_DIR.mkdir(exist_ok=True)
# 数据库配置
DATABASE_CONFIG = {
'host': os.getenv('DB_HOST', 'localhost'),
'host': os.getenv('DB_HOST', '10.100.51.161'),
'user': os.getenv('DB_USER', 'root'),
'password': os.getenv('DB_PASSWORD', 'sagacity'),
'database': os.getenv('DB_NAME', 'imeeting'),
@ -47,7 +47,7 @@ APP_CONFIG = {
# Redis配置
REDIS_CONFIG = {
'host': os.getenv('REDIS_HOST', 'localhost'),
'host': os.getenv('REDIS_HOST', '10.100.51.161'),
'port': int(os.getenv('REDIS_PORT', '6379')),
'db': int(os.getenv('REDIS_DB', '0')),
'password': os.getenv('REDIS_PASSWORD', None),
@ -80,3 +80,6 @@ LLM_CONFIG = {
- 如果某个部分没有相关内容可以说明"无相关内容"
- 总字数控制在500字以内"""
}
# 密码重置配置
DEFAULT_RESET_PASSWORD = os.getenv('DEFAULT_RESET_PASSWORD', '111111')

View File

@ -13,6 +13,7 @@ class LoginResponse(BaseModel):
caption: str
email: EmailStr
token: str
role_id: int
class UserInfo(BaseModel):
user_id: int
@ -23,6 +24,23 @@ class UserInfo(BaseModel):
meetings_created: int
meetings_attended: int
class UserListResponse(BaseModel):
users: list[UserInfo]
total: int
class CreateUserRequest(BaseModel):
username: str
password: Optional[str] = None
caption: str
email: EmailStr
role_id: int
class UpdateUserRequest(BaseModel):
username: Optional[str] = None
caption: Optional[str] = None
email: Optional[str] = None
role_id: Optional[int] = None
class AttendeeInfo(BaseModel):
user_id: int
caption: str
@ -83,3 +101,7 @@ class TranscriptUpdateRequest(BaseModel):
class BatchTranscriptUpdateRequest(BaseModel):
updates: List[TranscriptUpdateRequest]
class PasswordChangeRequest(BaseModel):
old_password: str
new_password: str

View File

@ -1,5 +1,3 @@
version: '3.8'
services:
imeeting-backend:
build:
@ -51,6 +49,8 @@ services:
- ./uploads:/app/uploads
restart: unless-stopped
container_name: imeeting-backend
extra_hosts:
- "host.docker.internal:host-gateway"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8001/health"]
interval: 30s

BIN
uploads/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +0,0 @@
fastapi
mysql-connector-python
uvicorn[standard]
python-multipart
pydantic[email]
passlib[bcrypt]
qiniu

Binary file not shown.

BIN
venv.zip 100644

Binary file not shown.