Appearance
第85天:企业知识库-管理后台与部署
学习目标
- 掌握管理后台开发
- 学习文档管理功能
- 理解用户管理功能
- 掌握部署配置
- 学习监控告警
管理后台开发
后台架构
python
from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2PasswordBearer
from typing import Dict, List, Optional
app = FastAPI(title="企业知识库管理后台")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class AdminBackend:
def __init__(
self,
document_storage,
vector_store,
user_manager
):
self.document_storage = document_storage
self.vector_store = vector_store
self.user_manager = user_manager
async def get_dashboard_stats(self) -> Dict:
total_documents = await self._get_total_documents()
total_users = await self._get_total_users()
total_queries = await self._get_total_queries()
storage_usage = await self._get_storage_usage()
return {
"total_documents": total_documents,
"total_users": total_users,
"total_queries": total_queries,
"storage_usage": storage_usage
}
async def _get_total_documents(self) -> int:
return 0
async def _get_total_users(self) -> int:
return 0
async def _get_total_queries(self) -> int:
return 0
async def _get_storage_usage(self) -> Dict:
return {
"total": 0,
"used": 0,
"available": 0
}文档管理API
python
from fastapi import APIRouter, UploadFile, File, Query
from pydantic import BaseModel, Field
router = APIRouter(prefix="/api/admin/documents", tags=["文档管理"])
class DocumentListResponse(BaseModel):
documents: List[Dict]
total: int
page: int
page_size: int
class DocumentUpdateRequest(BaseModel):
filename: Optional[str] = None
metadata: Optional[Dict] = None
status: Optional[str] = None
@router.get("", response_model=DocumentListResponse)
async def list_documents(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
status: Optional[str] = None,
user_id: Optional[str] = None,
token: str = Depends(oauth2_scheme)
):
documents = []
total = 0
return DocumentListResponse(
documents=documents,
total=total,
page=page,
page_size=page_size
)
@router.get("/{document_id}")
async def get_document(
document_id: str,
token: str = Depends(oauth2_scheme)
):
return {}
@router.put("/{document_id}")
async def update_document(
document_id: str,
request: DocumentUpdateRequest,
token: str = Depends(oauth2_scheme)
):
return {}
@router.delete("/{document_id}")
async def delete_document(
document_id: str,
token: str = Depends(oauth2_scheme)
):
return {}
@router.post("/{document_id}/reprocess")
async def reprocess_document(
document_id: str,
token: str = Depends(oauth2_scheme)
):
return {}用户管理API
python
from fastapi import APIRouter
from pydantic import BaseModel, Field
router = APIRouter(prefix="/api/admin/users", tags=["用户管理"])
class UserCreateRequest(BaseModel):
username: str = Field(..., description="用户名")
email: str = Field(..., description="邮箱")
password: str = Field(..., description="密码")
role: str = Field(..., description="角色")
class UserUpdateRequest(BaseModel):
email: Optional[str] = None
role: Optional[str] = None
status: Optional[str] = None
@router.get("")
async def list_users(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
role: Optional[str] = None,
status: Optional[str] = None,
token: str = Depends(oauth2_scheme)
):
return {}
@router.post("")
async def create_user(
request: UserCreateRequest,
token: str = Depends(oauth2_scheme)
):
return {}
@router.get("/{user_id}")
async def get_user(
user_id: str,
token: str = Depends(oauth2_scheme)
):
return {}
@router.put("/{user_id}")
async def update_user(
user_id: str,
request: UserUpdateRequest,
token: str = Depends(oauth2_scheme)
):
return {}
@router.delete("/{user_id}")
async def delete_user(
user_id: str,
token: str = Depends(oauth2_scheme)
):
return {}文档管理功能
文档列表组件
typescript
import React, { useState, useEffect } from 'react';
import { Table, Button, Space, Tag, Modal, message } from 'antd';
import { EyeOutlined, EditOutlined, DeleteOutlined, ReloadOutlined } from '@ant-design/icons';
interface Document {
id: string;
filename: string;
file_size: number;
upload_time: string;
user_id: string;
status: string;
metadata: Record<string, any>;
}
interface DocumentListProps {
apiBaseUrl?: string;
}
const DocumentList: React.FC<DocumentListProps> = ({
apiBaseUrl = 'http://localhost:8000/api/admin/documents'
}) => {
const [documents, setDocuments] = useState<Document[]>([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 20,
total: 0
});
const fetchDocuments = async (page = 1, pageSize = 20) => {
setLoading(true);
try {
const response = await fetch(
`${apiBaseUrl}?page=${page}&page_size=${pageSize}`
);
const data = await response.json();
setDocuments(data.documents);
setPagination({
current: page,
pageSize,
total: data.total
});
} catch (error) {
message.error('获取文档列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchDocuments();
}, []);
const handleView = (document: Document) => {
Modal.info({
title: '文档详情',
content: (
<div>
<p><strong>文件名:</strong>{document.filename}</p>
<p><strong>文件大小:</strong>{(document.file_size / 1024).toFixed(2)} KB</p>
<p><strong>上传时间:</strong>{document.upload_time}</p>
<p><strong>状态:</strong>
<Tag color={document.status === 'completed' ? 'green' : 'orange'}>
{document.status}
</Tag>
</p>
</div>
),
width: 600
});
};
const handleReprocess = async (documentId: string) => {
try {
const response = await fetch(
`${apiBaseUrl}/${documentId}/reprocess`,
{ method: 'POST' }
);
if (response.ok) {
message.success('文档重新处理成功');
fetchDocuments(pagination.current, pagination.pageSize);
} else {
message.error('文档重新处理失败');
}
} catch (error) {
message.error('文档重新处理失败');
}
};
const handleDelete = async (documentId: string) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这个文档吗?',
onOk: async () => {
try {
const response = await fetch(
`${apiBaseUrl}/${documentId}`,
{ method: 'DELETE' }
);
if (response.ok) {
message.success('文档删除成功');
fetchDocuments(pagination.current, pagination.pageSize);
} else {
message.error('文档删除失败');
}
} catch (error) {
message.error('文档删除失败');
}
}
});
};
const columns = [
{
title: '文件名',
dataIndex: 'filename',
key: 'filename'
},
{
title: '文件大小',
dataIndex: 'file_size',
key: 'file_size',
render: (size: number) => `${(size / 1024).toFixed(2)} KB`
},
{
title: '上传时间',
dataIndex: 'upload_time',
key: 'upload_time',
render: (time: string) => new Date(time).toLocaleString()
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => (
<Tag color={status === 'completed' ? 'green' : 'orange'}>
{status}
</Tag>
)
},
{
title: '操作',
key: 'action',
render: (_: any, record: Document) => (
<Space size="middle">
<Button
type="link"
icon={<EyeOutlined />}
onClick={() => handleView(record)}
>
查看
</Button>
<Button
type="link"
icon={<ReloadOutlined />}
onClick={() => handleReprocess(record.id)}
>
重新处理
</Button>
<Button
type="link"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(record.id)}
>
删除
</Button>
</Space>
)
}
];
return (
<div>
<Table
columns={columns}
dataSource={documents}
rowKey="id"
loading={loading}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total,
onChange: (page, pageSize) => {
fetchDocuments(page, pageSize);
}
}}
/>
</div>
);
};
export default DocumentList;文档上传组件
typescript
import React, { useState } from 'react';
import { Upload, Button, message, Progress } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
interface DocumentUploadProps {
apiBaseUrl?: string;
onUploadSuccess?: () => void;
}
const DocumentUpload: React.FC<DocumentUploadProps> = ({
apiBaseUrl = 'http://localhost:8000/api/documents',
onUploadSuccess
}) => {
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const handleUpload = async (file: File) => {
const formData = new FormData();
formData.append('file', file);
setUploading(true);
setProgress(0);
try {
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
setProgress(percentComplete);
}
};
xhr.onload = async () => {
if (xhr.status === 200) {
message.success('文档上传成功');
setProgress(100);
if (onUploadSuccess) {
onUploadSuccess();
}
} else {
message.error('文档上传失败');
}
setUploading(false);
};
xhr.onerror = () => {
message.error('文档上传失败');
setUploading(false);
};
xhr.open('POST', apiBaseUrl);
xhr.send(formData);
} catch (error) {
message.error('文档上传失败');
setUploading(false);
}
return false;
};
return (
<div>
<Upload
beforeUpload={handleUpload}
disabled={uploading}
showUploadList={false}
>
<Button
icon={<UploadOutlined />}
loading={uploading}
>
{uploading ? '上传中...' : '上传文档'}
</Button>
</Upload>
{uploading && (
<Progress
percent={progress}
status="active"
style={{ marginTop: 16 }}
/>
)}
</div>
);
};
export default DocumentUpload;用户管理功能
用户列表组件
typescript
import React, { useState, useEffect } from 'react';
import { Table, Button, Space, Tag, Modal, Form, Input, Select, message } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
interface User {
id: string;
username: string;
email: string;
role: string;
status: string;
created_at: string;
}
interface UserListProps {
apiBaseUrl?: string;
}
const UserList: React.FC<UserListProps> = ({
apiBaseUrl = 'http://localhost:8000/api/admin/users'
}) => {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [editingUser, setEditingUser] = useState<User | null>(null);
const [form] = Form.useForm();
const fetchUsers = async () => {
setLoading(true);
try {
const response = await fetch(apiBaseUrl);
const data = await response.json();
setUsers(data.users || []);
} catch (error) {
message.error('获取用户列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchUsers();
}, []);
const handleCreate = () => {
setEditingUser(null);
form.resetFields();
setModalVisible(true);
};
const handleEdit = (user: User) => {
setEditingUser(user);
form.setFieldsValue(user);
setModalVisible(true);
};
const handleDelete = async (userId: string) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这个用户吗?',
onOk: async () => {
try {
const response = await fetch(
`${apiBaseUrl}/${userId}`,
{ method: 'DELETE' }
);
if (response.ok) {
message.success('用户删除成功');
fetchUsers();
} else {
message.error('用户删除失败');
}
} catch (error) {
message.error('用户删除失败');
}
}
});
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
if (editingUser) {
const response = await fetch(
`${apiBaseUrl}/${editingUser.id}`,
{
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values)
}
);
if (response.ok) {
message.success('用户更新成功');
fetchUsers();
setModalVisible(false);
} else {
message.error('用户更新失败');
}
} else {
const response = await fetch(
apiBaseUrl,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values)
}
);
if (response.ok) {
message.success('用户创建成功');
fetchUsers();
setModalVisible(false);
} else {
message.error('用户创建失败');
}
}
} catch (error) {
message.error('操作失败');
}
};
const columns = [
{
title: '用户名',
dataIndex: 'username',
key: 'username'
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email'
},
{
title: '角色',
dataIndex: 'role',
key: 'role',
render: (role: string) => (
<Tag color={role === 'admin' ? 'red' : 'blue'}>
{role}
</Tag>
)
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'default'}>
{status}
</Tag>
)
},
{
title: '创建时间',
dataIndex: 'created_at',
key: 'created_at',
render: (time: string) => new Date(time).toLocaleString()
},
{
title: '操作',
key: 'action',
render: (_: any, record: User) => (
<Space size="middle">
<Button
type="link"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
编辑
</Button>
<Button
type="link"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(record.id)}
>
删除
</Button>
</Space>
)
}
];
return (
<div>
<div style={{ marginBottom: 16 }}>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleCreate}
>
新建用户
</Button>
</div>
<Table
columns={columns}
dataSource={users}
rowKey="id"
loading={loading}
/>
<Modal
title={editingUser ? '编辑用户' : '新建用户'}
open={modalVisible}
onOk={handleSubmit}
onCancel={() => setModalVisible(false)}
width={600}
>
<Form form={form} layout="vertical">
<Form.Item
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input placeholder="请输入用户名" />
</Form.Item>
<Form.Item
name="email"
label="邮箱"
rules={[
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '请输入有效的邮箱地址' }
]}
>
<Input placeholder="请输入邮箱" />
</Form.Item>
{!editingUser && (
<Form.Item
name="password"
label="密码"
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password placeholder="请输入密码" />
</Form.Item>
)}
<Form.Item
name="role"
label="角色"
rules={[{ required: true, message: '请选择角色' }]}
>
<Select placeholder="请选择角色">
<Select.Option value="admin">管理员</Select.Option>
<Select.Option value="editor">编辑</Select.Option>
<Select.Option value="viewer">查看者</Select.Option>
</Select>
</Form.Item>
<Form.Item
name="status"
label="状态"
rules={[{ required: true, message: '请选择状态' }]}
>
<Select placeholder="请选择状态">
<Select.Option value="active">活跃</Select.Option>
<Select.Option value="inactive">非活跃</Select.Option>
</Select>
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default UserList;部署配置
Docker配置
dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]yaml
version: '3.8'
services:
api:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:password@postgres:5432/knowledge_base
- REDIS_URL=redis://redis:6379
- OPENAI_API_KEY=${OPENAI_API_KEY}
depends_on:
- postgres
- redis
- chroma
volumes:
- ./data:/app/data
restart: unless-stopped
postgres:
image: postgres:15
environment:
- POSTGRES_DB=knowledge_base
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped
chroma:
image: chromadb/chroma:latest
ports:
- "8001:8000"
volumes:
- chroma_data:/chroma/chroma
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- api
restart: unless-stopped
volumes:
postgres_data:
redis_data:
chroma_data:Kubernetes配置
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: knowledge-base
namespace: knowledge-base
spec:
replicas: 3
selector:
matchLabels:
app: knowledge-base
template:
metadata:
labels:
app: knowledge-base
spec:
containers:
- name: api
image: your-registry/knowledge-base:latest
ports:
- containerPort: 8000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: knowledge-base-secrets
key: database-url
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: knowledge-base-secrets
key: openai-api-key
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 5监控告警
性能监控
python
import time
from typing import Dict, List
from collections import deque
class PerformanceMonitor:
def __init__(self, max_history: int = 1000):
self.max_history = max_history
self.metrics = {
"query_times": deque(maxlen=max_history),
"document_processing_times": deque(maxlen=max_history),
"error_rates": deque(maxlen=max_history),
"active_users": deque(maxlen=max_history)
}
def record_query_time(self, query_time: float):
self.metrics["query_times"].append({
"value": query_time,
"timestamp": time.time()
})
def record_document_processing_time(self, processing_time: float):
self.metrics["document_processing_times"].append({
"value": processing_time,
"timestamp": time.time()
})
def record_error(self, error_type: str):
self.metrics["error_rates"].append({
"error_type": error_type,
"timestamp": time.time()
})
def record_active_users(self, count: int):
self.metrics["active_users"].append({
"value": count,
"timestamp": time.time()
})
def get_statistics(self) -> Dict:
stats = {}
for metric_name, metric_data in self.metrics.items():
if not metric_data:
stats[metric_name] = {
"count": 0,
"avg": 0,
"min": 0,
"max": 0
}
continue
values = [
item["value"]
for item in metric_data
if "value" in item
]
if values:
stats[metric_name] = {
"count": len(values),
"avg": sum(values) / len(values),
"min": min(values),
"max": max(values)
}
else:
stats[metric_name] = {
"count": len(metric_data),
"total": len(metric_data)
}
return stats实践练习
练习1:实现文档管理
python
def implement_document_management():
document_storage = DocumentStorage()
vector_store = VectorStore()
return document_storage, vector_store练习2:实现用户管理
python
def implement_user_management():
user_manager = UserManager()
return user_manager练习3:部署知识库
bash
docker-compose up -d总结
本节我们学习了企业知识库的管理后台与部署:
- 管理后台开发
- 文档管理功能
- 用户管理功能
- 部署配置
- 监控告警
管理后台和部署是知识库上线的关键环节。
