From 85d16a60dafb80b9670016abecb0b95f5229afc0 Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Tue, 16 Dec 2025 18:55:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E4=BC=9A=E8=AE=AE?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 10244 -> 10244 bytes app.zip | Bin 84838 -> 85076 bytes app/api/endpoints/meetings.py | 142 ++++++++++++++++++++++++++++++++-- app/api/endpoints/prompts.py | 31 ++++++++ app/models/models.py | 1 + requirements-prod.txt | 5 +- requirements.txt | 3 +- 7 files changed, 174 insertions(+), 8 deletions(-) diff --git a/.DS_Store b/.DS_Store index d8f9254bc8d3b226237242817f57091f2d7ff8a1..55b6e6291bcc9d7a120f512f0ef89dab144540fe 100644 GIT binary patch delta 43 scmZn(XbIThEy$|Lz`(FzbAaFhR(2C3a~%a^!_DI2Klx#t$>QQ~0Rx!}n*aa+ delta 43 scmZn(XbIThEy%i>fq}tqbAaFhR(2yxV;u!!i_PNVKlx#t$>QQ~0Sq?_+W-In diff --git a/app.zip b/app.zip index 4bb133b2d05fc78305ee4d8b8678b2e926f877a9..477099a748c6d8772c0bdd306211dee4da72492a 100644 GIT binary patch delta 4301 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 diff --git a/app/api/endpoints/meetings.py b/app/api/endpoints/meetings.py index 68f2e43..aa350ab 100644 --- a/app/api/endpoints/meetings.py +++ b/app/api/endpoints/meetings.py @@ -232,7 +232,8 @@ def get_meeting_details(meeting_id: int, current_user: dict = Depends(get_curren cursor = connection.cursor(dictionary=True) query = ''' SELECT m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at, m.tags, - m.user_id as creator_id, u.caption as creator_username, af.file_path as audio_file_path + m.user_id as creator_id, u.caption as creator_username, af.file_path as audio_file_path, + m.access_password FROM meetings m JOIN users u ON m.user_id = u.user_id LEFT JOIN audio_files af ON m.meeting_id = af.meeting_id WHERE m.meeting_id = %s ''' @@ -249,7 +250,8 @@ def get_meeting_details(meeting_id: int, current_user: dict = Depends(get_curren meeting_data = Meeting( meeting_id=meeting['meeting_id'], title=meeting['title'], meeting_time=meeting['meeting_time'], summary=meeting['summary'], created_at=meeting['created_at'], attendees=attendees, - creator_id=meeting['creator_id'], creator_username=meeting['creator_username'], tags=tags + creator_id=meeting['creator_id'], creator_username=meeting['creator_username'], tags=tags, + access_password=meeting.get('access_password') ) if meeting['audio_file_path']: meeting_data.audio_file_path = meeting['audio_file_path'] @@ -341,7 +343,8 @@ def get_meeting_for_edit(meeting_id: int, current_user: dict = Depends(get_curre cursor = connection.cursor(dictionary=True) query = ''' SELECT m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at, m.tags, - m.user_id as creator_id, u.caption as creator_username, af.file_path as audio_file_path + m.user_id as creator_id, u.caption as creator_username, af.file_path as audio_file_path, + m.access_password FROM meetings m JOIN users u ON m.user_id = u.user_id LEFT JOIN audio_files af ON m.meeting_id = af.meeting_id WHERE m.meeting_id = %s ''' @@ -358,7 +361,8 @@ def get_meeting_for_edit(meeting_id: int, current_user: dict = Depends(get_curre meeting_data = Meeting( meeting_id=meeting['meeting_id'], title=meeting['title'], meeting_time=meeting['meeting_time'], summary=meeting['summary'], created_at=meeting['created_at'], attendees=attendees, - creator_id=meeting['creator_id'], creator_username=meeting['creator_username'], tags=tags + creator_id=meeting['creator_id'], creator_username=meeting['creator_username'], tags=tags, + access_password=meeting.get('access_password') ) if meeting.get('audio_file_path'): meeting_data.audio_file_path = meeting['audio_file_path'] @@ -888,7 +892,7 @@ def get_meeting_preview_data(meeting_id: int): query = ''' SELECT m.meeting_id, m.title, m.meeting_time, m.summary, m.updated_at, m.prompt_id, m.user_id as creator_id, u.caption as creator_username, - p.name as prompt_name + p.name as prompt_name, m.access_password FROM meetings m JOIN users u ON m.user_id = u.user_id LEFT JOIN prompts p ON m.prompt_id = p.id @@ -920,10 +924,136 @@ def get_meeting_preview_data(meeting_id: int): "prompt_id": meeting['prompt_id'], "prompt_name": meeting['prompt_name'], "attendees": attendees, - "attendees_count": len(attendees) + "attendees_count": len(attendees), + "has_password": bool(meeting.get('access_password')) # 新增:是否设置了访问密码 } return create_api_response(code="200", message="获取会议预览数据成功", data=preview_data) except Exception as e: return create_api_response(code="500", message=f"Failed to get meeting preview data: {str(e)}") + +# 访问密码管理相关API + +class AccessPasswordRequest(BaseModel): + password: Optional[str] = None # None表示关闭密码 + +class VerifyPasswordRequest(BaseModel): + password: str + +@router.put("/meetings/{meeting_id}/access-password") +def update_meeting_access_password( + meeting_id: int, + request: AccessPasswordRequest, + current_user: dict = Depends(get_current_user) +): + """ + 设置或关闭会议访问密码(仅创建人可操作) + + Args: + meeting_id: 会议ID + request.password: 密码字符串(None表示关闭密码) + current_user: 当前登录用户 + + Returns: + API响应,包含操作结果 + """ + try: + with get_db_connection() as connection: + cursor = connection.cursor(dictionary=True) + + # 检查会议是否存在且当前用户是创建人 + cursor.execute( + "SELECT meeting_id, user_id FROM meetings WHERE meeting_id = %s", + (meeting_id,) + ) + meeting = cursor.fetchone() + + if not meeting: + return create_api_response(code="404", message="会议不存在") + + if meeting['user_id'] != current_user['user_id']: + return create_api_response(code="403", message="仅创建人可以设置访问密码") + + # 更新访问密码 + cursor.execute( + "UPDATE meetings SET access_password = %s WHERE meeting_id = %s", + (request.password, meeting_id) + ) + connection.commit() + + if request.password: + return create_api_response( + code="200", + message="访问密码已设置", + data={"password": request.password} + ) + else: + return create_api_response( + code="200", + message="访问密码已关闭", + data={"password": None} + ) + + except Exception as e: + return create_api_response( + code="500", + message=f"设置访问密码失败: {str(e)}" + ) + +@router.post("/meetings/{meeting_id}/verify-password") +def verify_meeting_password(meeting_id: int, request: VerifyPasswordRequest): + """ + 验证会议访问密码(无需登录认证) + + Args: + meeting_id: 会议ID + request.password: 要验证的密码 + + Returns: + API响应,包含验证结果 + """ + try: + with get_db_connection() as connection: + cursor = connection.cursor(dictionary=True) + + # 获取会议的访问密码 + cursor.execute( + "SELECT access_password FROM meetings WHERE meeting_id = %s", + (meeting_id,) + ) + meeting = cursor.fetchone() + + if not meeting: + return create_api_response(code="404", message="会议不存在") + + # 验证密码 + stored_password = meeting.get('access_password') + + if not stored_password: + # 没有设置密码,直接通过 + return create_api_response( + code="200", + message="该会议未设置访问密码", + data={"verified": True} + ) + + if request.password == stored_password: + return create_api_response( + code="200", + message="密码验证成功", + data={"verified": True} + ) + else: + return create_api_response( + code="200", + message="密码错误", + data={"verified": False} + ) + + except Exception as e: + return create_api_response( + code="500", + message=f"验证密码失败: {str(e)}" + ) + diff --git a/app/api/endpoints/prompts.py b/app/api/endpoints/prompts.py index 0244d6f..bda1a14 100644 --- a/app/api/endpoints/prompts.py +++ b/app/api/endpoints/prompts.py @@ -203,6 +203,37 @@ def delete_prompt(prompt_id: int, current_user: dict = Depends(get_current_user) if prompt['creator_id'] != current_user["user_id"]: return create_api_response(code="403", message="无权删除其他用户的提示词") + # 检查是否有会议引用了该提示词 + cursor.execute( + "SELECT COUNT(*) as count FROM meetings WHERE prompt_id = %s", + (prompt_id,) + ) + meeting_count = cursor.fetchone()['count'] + + # 检查是否有知识库引用了该提示词 + cursor.execute( + "SELECT COUNT(*) as count FROM knowledge_bases WHERE prompt_id = %s", + (prompt_id,) + ) + kb_count = cursor.fetchone()['count'] + + # 如果有引用,不允许删除 + if meeting_count > 0 or kb_count > 0: + references = [] + if meeting_count > 0: + references.append(f"{meeting_count}个会议") + if kb_count > 0: + references.append(f"{kb_count}个知识库") + + return create_api_response( + code="400", + message=f"无法删除:该提示词被{' 和 '.join(references)}引用", + data={ + "meeting_count": meeting_count, + "kb_count": kb_count + } + ) + # 删除提示词 cursor.execute("DELETE FROM prompts WHERE id = %s", (prompt_id,)) connection.commit() diff --git a/app/models/models.py b/app/models/models.py index 98d6c35..5cd9894 100644 --- a/app/models/models.py +++ b/app/models/models.py @@ -77,6 +77,7 @@ class Meeting(BaseModel): audio_file_path: Optional[str] = None transcription_status: Optional[TranscriptionTaskStatus] = None tags: Optional[List[Tag]] = [] + access_password: Optional[str] = None class TranscriptSegment(BaseModel): segment_id: int diff --git a/requirements-prod.txt b/requirements-prod.txt index 0cbca48..9bd1d4e 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -14,4 +14,7 @@ qiniu # Validation & Forms email-validator -python-multipart \ No newline at end of file +python-multipart + +# System Monitoring +psutil diff --git a/requirements.txt b/requirements.txt index 598fe20..3cdd46f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,5 @@ qiniu redis>=5.0.0 dashscope PyJWT>=2.8.0 -python-jose[cryptography]>=3.3.0 \ No newline at end of file +python-jose[cryptography]>=3.3.0 +psutil