增加了客户端管理模块

main
mula.liu 2025-10-21 17:28:52 +08:00
parent 0d1b8c815d
commit 5bdab4a405
5 changed files with 587 additions and 1 deletions

View File

@ -9,6 +9,8 @@ ENV PYTHONPATH=/app
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
# 使用阿里源 # 使用阿里源
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list.d/debian.sources
RUN sed -i 's/security.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list.d/debian.sources
# 安装系统依赖 # 安装系统依赖
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \

View File

@ -0,0 +1,391 @@
from fastapi import APIRouter, HTTPException, Depends
from app.models.models import (
ClientDownload,
CreateClientDownloadRequest,
UpdateClientDownloadRequest,
ClientDownloadListResponse
)
from app.core.database import get_db_connection
from app.core.auth import get_current_user, get_current_admin_user
from app.core.response import create_api_response
from typing import Optional
router = APIRouter()
@router.get("/downloads", response_model=dict)
async def get_client_downloads(
platform_type: Optional[str] = None,
platform_name: Optional[str] = None,
is_active: Optional[bool] = None,
page: int = 1,
size: int = 50
):
"""
获取客户端下载列表公开接口所有用户可访问
"""
try:
with get_db_connection() as conn:
cursor = conn.cursor(dictionary=True)
# 构建查询条件
where_clauses = []
params = []
if platform_type:
where_clauses.append("platform_type = %s")
params.append(platform_type)
if platform_name:
where_clauses.append("platform_name = %s")
params.append(platform_name)
if is_active is not None:
where_clauses.append("is_active = %s")
params.append(is_active)
where_clause = " AND ".join(where_clauses) if where_clauses else "1=1"
# 获取总数
count_query = f"SELECT COUNT(*) as total FROM client_downloads WHERE {where_clause}"
cursor.execute(count_query, params)
total = cursor.fetchone()['total']
# 获取列表数据
offset = (page - 1) * size
list_query = f"""
SELECT * FROM client_downloads
WHERE {where_clause}
ORDER BY platform_type, platform_name, version_code DESC
LIMIT %s OFFSET %s
"""
cursor.execute(list_query, params + [size, offset])
clients = cursor.fetchall()
cursor.close()
return create_api_response(
code="200",
message="获取成功",
data={
"clients": clients,
"total": total,
"page": page,
"size": size
}
)
except Exception as e:
return create_api_response(
code="500",
message=f"获取客户端下载列表失败: {str(e)}"
)
@router.get("/downloads/latest", response_model=dict)
async def get_latest_clients():
"""
获取所有平台的最新版本客户端公开接口用于首页下载
"""
try:
with get_db_connection() as conn:
cursor = conn.cursor(dictionary=True)
query = """
SELECT * FROM client_downloads
WHERE is_active = TRUE AND is_latest = TRUE
ORDER BY platform_type, platform_name
"""
cursor.execute(query)
clients = cursor.fetchall()
cursor.close()
# 按平台类型分组
mobile_clients = []
desktop_clients = []
for client in clients:
if client['platform_type'] == 'mobile':
mobile_clients.append(client)
else:
desktop_clients.append(client)
return create_api_response(
code="200",
message="获取成功",
data={
"mobile": mobile_clients,
"desktop": desktop_clients
}
)
except Exception as e:
return create_api_response(
code="500",
message=f"获取最新客户端失败: {str(e)}"
)
@router.get("/downloads/{platform_name}/latest", response_model=dict)
async def get_latest_version_by_platform(platform_name: str):
"""
获取指定平台的最新版本公开接口用于客户端版本检查
"""
try:
with get_db_connection() as conn:
cursor = conn.cursor(dictionary=True)
query = """
SELECT * FROM client_downloads
WHERE platform_name = %s AND is_active = TRUE AND is_latest = TRUE
LIMIT 1
"""
cursor.execute(query, (platform_name,))
client = cursor.fetchone()
cursor.close()
if not client:
return create_api_response(
code="404",
message=f"未找到平台 {platform_name} 的客户端"
)
return create_api_response(
code="200",
message="获取成功",
data=client
)
except Exception as e:
return create_api_response(
code="500",
message=f"获取客户端版本失败: {str(e)}"
)
@router.get("/downloads/{id}", response_model=dict)
async def get_client_download_by_id(id: int):
"""
获取指定ID的客户端详情公开接口
"""
try:
with get_db_connection() as conn:
cursor = conn.cursor(dictionary=True)
query = "SELECT * FROM client_downloads WHERE id = %s"
cursor.execute(query, (id,))
client = cursor.fetchone()
cursor.close()
if not client:
return create_api_response(
code="404",
message="客户端不存在"
)
return create_api_response(
code="200",
message="获取成功",
data=client
)
except Exception as e:
return create_api_response(
code="500",
message=f"获取客户端详情失败: {str(e)}"
)
@router.post("/downloads", response_model=dict)
async def create_client_download(
request: CreateClientDownloadRequest,
current_user: dict = Depends(get_current_admin_user)
):
"""
创建新的客户端版本仅管理员
"""
try:
with get_db_connection() as conn:
cursor = conn.cursor()
# 如果设置为最新版本,先将同平台的其他版本设为非最新
if request.is_latest:
update_query = """
UPDATE client_downloads
SET is_latest = FALSE
WHERE platform_name = %s
"""
cursor.execute(update_query, (request.platform_name,))
# 插入新版本
insert_query = """
INSERT INTO client_downloads (
platform_type, platform_name, version, version_code,
download_url, file_size, release_notes, is_active,
is_latest, min_system_version, created_by
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
cursor.execute(insert_query, (
request.platform_type,
request.platform_name,
request.version,
request.version_code,
request.download_url,
request.file_size,
request.release_notes,
request.is_active,
request.is_latest,
request.min_system_version,
current_user['user_id']
))
new_id = cursor.lastrowid
conn.commit()
cursor.close()
return create_api_response(
code="200",
message="客户端版本创建成功",
data={"id": new_id}
)
except Exception as e:
return create_api_response(
code="500",
message=f"创建客户端版本失败: {str(e)}"
)
@router.put("/downloads/{id}", response_model=dict)
async def update_client_download(
id: int,
request: UpdateClientDownloadRequest,
current_user: dict = Depends(get_current_admin_user)
):
"""
更新客户端版本信息仅管理员
"""
try:
with get_db_connection() as conn:
cursor = conn.cursor(dictionary=True)
# 检查客户端是否存在
cursor.execute("SELECT * FROM client_downloads WHERE id = %s", (id,))
existing = cursor.fetchone()
if not existing:
cursor.close()
return create_api_response(
code="404",
message="客户端不存在"
)
# 如果设置为最新版本,先将同平台的其他版本设为非最新
if request.is_latest:
update_query = """
UPDATE client_downloads
SET is_latest = FALSE
WHERE platform_name = %s AND id != %s
"""
cursor.execute(update_query, (existing['platform_name'], id))
# 构建更新语句
update_fields = []
params = []
if request.version is not None:
update_fields.append("version = %s")
params.append(request.version)
if request.version_code is not None:
update_fields.append("version_code = %s")
params.append(request.version_code)
if request.download_url is not None:
update_fields.append("download_url = %s")
params.append(request.download_url)
if request.file_size is not None:
update_fields.append("file_size = %s")
params.append(request.file_size)
if request.release_notes is not None:
update_fields.append("release_notes = %s")
params.append(request.release_notes)
if request.is_active is not None:
update_fields.append("is_active = %s")
params.append(request.is_active)
if request.is_latest is not None:
update_fields.append("is_latest = %s")
params.append(request.is_latest)
if request.min_system_version is not None:
update_fields.append("min_system_version = %s")
params.append(request.min_system_version)
if not update_fields:
cursor.close()
return create_api_response(
code="400",
message="没有要更新的字段"
)
# 执行更新
update_query = f"""
UPDATE client_downloads
SET {', '.join(update_fields)}
WHERE id = %s
"""
params.append(id)
cursor.execute(update_query, params)
conn.commit()
cursor.close()
return create_api_response(
code="200",
message="客户端版本更新成功"
)
except Exception as e:
return create_api_response(
code="500",
message=f"更新客户端版本失败: {str(e)}"
)
@router.delete("/downloads/{id}", response_model=dict)
async def delete_client_download(
id: int,
current_user: dict = Depends(get_current_admin_user)
):
"""
删除客户端版本仅管理员
"""
try:
with get_db_connection() as conn:
cursor = conn.cursor()
# 检查是否存在
cursor.execute("SELECT * FROM client_downloads WHERE id = %s", (id,))
if not cursor.fetchone():
cursor.close()
return create_api_response(
code="404",
message="客户端不存在"
)
# 执行删除
cursor.execute("DELETE FROM client_downloads WHERE id = %s", (id,))
conn.commit()
cursor.close()
return create_api_response(
code="200",
message="客户端版本删除成功"
)
except Exception as e:
return create_api_response(
code="500",
message=f"删除客户端版本失败: {str(e)}"
)

View File

@ -161,3 +161,46 @@ class UpdateKnowledgeBaseRequest(BaseModel):
class KnowledgeBaseListResponse(BaseModel): class KnowledgeBaseListResponse(BaseModel):
kbs: List[KnowledgeBase] kbs: List[KnowledgeBase]
total: int total: int
# 客户端下载相关模型
class ClientDownload(BaseModel):
id: int
platform_type: str # 'mobile' or 'desktop'
platform_name: str # 'ios', 'android', 'windows', 'mac_intel', 'mac_m', 'linux'
version: str
version_code: int
download_url: str
file_size: Optional[int] = None
release_notes: Optional[str] = None
is_active: bool
is_latest: bool
min_system_version: Optional[str] = None
created_at: datetime.datetime
updated_at: datetime.datetime
created_by: Optional[int] = None
class CreateClientDownloadRequest(BaseModel):
platform_type: str
platform_name: str
version: str
version_code: int
download_url: str
file_size: Optional[int] = None
release_notes: Optional[str] = None
is_active: bool = True
is_latest: bool = False
min_system_version: Optional[str] = None
class UpdateClientDownloadRequest(BaseModel):
version: Optional[str] = None
version_code: Optional[int] = None
download_url: Optional[str] = None
file_size: Optional[int] = None
release_notes: Optional[str] = None
is_active: Optional[bool] = None
is_latest: Optional[bool] = None
min_system_version: Optional[str] = None
class ClientDownloadListResponse(BaseModel):
clients: List[ClientDownload]
total: int

View File

@ -2,7 +2,7 @@ import uvicorn
from fastapi import FastAPI, Request, HTTPException from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from app.api.endpoints import auth, users, meetings, tags, admin, tasks, prompts, knowledge_base from app.api.endpoints import auth, users, meetings, tags, admin, tasks, prompts, knowledge_base, client_downloads
from app.core.config import UPLOAD_DIR, API_CONFIG from app.core.config import UPLOAD_DIR, API_CONFIG
from app.api.endpoints.admin import load_system_config from app.api.endpoints.admin import load_system_config
import os import os
@ -38,6 +38,7 @@ app.include_router(admin.router, prefix="/api", tags=["Admin"])
app.include_router(tasks.router, prefix="/api", tags=["Tasks"]) app.include_router(tasks.router, prefix="/api", tags=["Tasks"])
app.include_router(prompts.router, prefix="/api", tags=["Prompts"]) app.include_router(prompts.router, prefix="/api", tags=["Prompts"])
app.include_router(knowledge_base.router, prefix="/api", tags=["KnowledgeBase"]) app.include_router(knowledge_base.router, prefix="/api", tags=["KnowledgeBase"])
app.include_router(client_downloads.router, prefix="/api/clients", tags=["ClientDownloads"])
@app.get("/") @app.get("/")
def read_root(): def read_root():

View File

@ -0,0 +1,149 @@
-- 客户端下载管理表
CREATE TABLE IF NOT EXISTS client_downloads (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
platform_type ENUM('mobile', 'desktop') NOT NULL COMMENT '平台类型mobile-移动端, desktop-桌面端',
platform_name VARCHAR(50) NOT NULL COMMENT '具体平台ios, android, windows, mac_intel, mac_m, linux',
version VARCHAR(50) NOT NULL COMMENT '版本号,如: 1.0.0',
version_code INT NOT NULL DEFAULT 1 COMMENT '版本代码,用于版本比较',
download_url TEXT NOT NULL COMMENT '下载链接',
file_size BIGINT COMMENT '文件大小(字节)',
release_notes TEXT COMMENT '更新说明',
is_active BOOLEAN DEFAULT TRUE COMMENT '是否启用',
is_latest BOOLEAN DEFAULT FALSE COMMENT '是否为最新版本',
min_system_version VARCHAR(50) COMMENT '最低系统版本要求',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
created_by INT COMMENT '创建人ID',
FOREIGN KEY (created_by) REFERENCES users(user_id) ON DELETE SET NULL,
INDEX idx_platform (platform_type, platform_name),
INDEX idx_version (version_code),
INDEX idx_active (is_active, is_latest)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户端下载管理表';
-- 插入初始数据(示例版本)
INSERT INTO client_downloads (
platform_type,
platform_name,
version,
version_code,
download_url,
file_size,
release_notes,
is_active,
is_latest,
min_system_version,
created_by
) VALUES
-- iOS 客户端
(
'mobile',
'ios',
'1.0.0',
1000,
'https://apps.apple.com/app/imeeting/id123456789',
52428800, -- 50MB
'初始版本发布
-
-
- ',
TRUE,
TRUE,
'iOS 13.0',
1
),
-- Android 客户端
(
'mobile',
'android',
'1.0.0',
1000,
'https://play.google.com/store/apps/details?id=com.imeeting.app',
45088768, -- 43MB
'初始版本发布
-
-
- ',
TRUE,
TRUE,
'Android 8.0',
1
),
-- Windows 客户端
(
'desktop',
'windows',
'1.0.0',
1000,
'https://download.imeeting.com/clients/windows/iMeeting-1.0.0-Setup.exe',
104857600, -- 100MB
'初始版本发布
-
-
- AI
- ',
TRUE,
TRUE,
'Windows 10 (64-bit)',
1
),
-- Mac Intel 客户端
(
'desktop',
'mac_intel',
'1.0.0',
1000,
'https://download.imeeting.com/clients/mac/iMeeting-1.0.0-Intel.dmg',
94371840, -- 90MB
'初始版本发布
-
-
- AI
- ',
TRUE,
TRUE,
'macOS 10.15 Catalina',
1
),
-- Mac M系列 客户端
(
'desktop',
'mac_m',
'1.0.0',
1000,
'https://download.imeeting.com/clients/mac/iMeeting-1.0.0-AppleSilicon.dmg',
83886080, -- 80MB
'初始版本发布
-
-
- AI
-
- Apple Silicon',
TRUE,
TRUE,
'macOS 11.0 Big Sur',
1
),
-- Linux 客户端
(
'desktop',
'linux',
'1.0.0',
1000,
'https://download.imeeting.com/clients/linux/iMeeting-1.0.0-x64.AppImage',
98566144, -- 94MB
'初始版本发布
-
-
- AI
-
- Linux',
TRUE,
TRUE,
'Ubuntu 20.04 / Debian 10 / Fedora 32 或更高版本',
1
);