v1.0.3
parent
a192590c3b
commit
aad59dd5df
|
|
@ -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 . .
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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": "密码修改成功"}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,7 +0,0 @@
|
|||
fastapi
|
||||
mysql-connector-python
|
||||
uvicorn[standard]
|
||||
python-multipart
|
||||
pydantic[email]
|
||||
passlib[bcrypt]
|
||||
qiniu
|
||||
Binary file not shown.
Loading…
Reference in New Issue