一、系统架构与技术选型
新闻出版部门档案管理具有文件类型多(PDF、TIFF、音频、视频)、数据量大、安全性要求高的特点。本方案采用FastAPI作为后端框架,PostgreSQL存储结构化元数据,MinIO存储非结构化文件,Docker进行容器化部署。这套组合具备高性能、易扩展、零成本的特点,能够满足日均百万级文件的归档需求。
二、基础环境部署
我们需要准备服务器环境。推荐配置为4核8G内存,100G SSD硬盘。系统安装CentOS 7.9或Ubuntu 20.04 LTS。为了确保环境一致性,我们使用Docker Compose一键编排数据库和对象存储服务。
在服务器根目录下创建archive_system文件夹,并新建docker-compose.yml文件,直接复制以下内容:
```yaml
version: '3.8'
services:
PostgreSQL 数据库
db:
image: postgres:14-al
container_name: archive_db
environment:
POSTGRES_USER: archive_admin
POSTGRES_PASSWORD: SecurePassword123!
POSTGRES_DB: archive_db
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: always
MinIO 对象存储
minio:
image: minio/minio:latest
container_name: archive_minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: MinioAdmin123!
volumes:
- miniodata:/data
ports:
- "9000:9000"
- "9001:9001"
restart: always
volumes:
pgdata:
miniodata:
```
保存文件后,执行以下命令启动基础服务:
```bash
docker-compose up -d
```
执行docker ps确认两个容器状态均为Up。此时,数据库运行在5432端口,MinIO控制台运行在9001端口,API接口在9000端口。
三、数据库表结构设计
档案制度建设的核心在于元数据的标准化。我们需要设计一张archives表,用于存储档案的分类、题名、责任者、归档时间等关键信息。连接数据库并执行以下SQL语句:
```sql
CREATE TABLE archives (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL, -- 档案题名
category VARCHAR(50) NOT NULL, -- 档案分类:如'报纸期刊', '音像资料', '行政文件'
file_type VARCHAR(20) NOT NULL, -- 文件类型:pdf, mp4, jpg
file_path VARCHAR(512) NOT NULL, -- MinIO存储路径
file_size BIGINT, -- 文件大小(字节)
department VARCHAR(100), -- 责任部门
creator VARCHAR(50), -- 创建人
description TEXT, -- 档案描述
security_level VARCHAR(20) DEFAULT '内部', -- 密级:公开, 内部, 机密
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建索引以加速检索
CREATE INDEX idx_category ON archives(category);
CREATE INDEX idx_created_at ON archives(created_at);
```
四、后端核心代码开发
安装Python 3.9及以上环境,创建项目目录并安装依赖:
```bash
pip install fastapi uvicorn sqlalchemy psycopg2-binary minio python-multipart
```
在项目目录下创建main.py,编写完整的业务逻辑。代码包含数据库连接、MinIO客户端初始化、文件上传及归档接口。
```python
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.responses import JSONResponse
from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, BigInteger
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from minio import Minio
from minio.error import S3Error
import uuid
import os
from datetime import datetime
配置部分
DATABASE_URL = "postgresql://archive_admin:SecurePassword123!@localhost:5432/archive_db"
MINIO_ENDPOINT = "localhost:9000"
MINIO_ACCESS_KEY = "minioadmin"
MINIO_SECRET_KEY = "MinioAdmin123!"
MINIO_BUCKET_NAME = "news-archives"
数据库初始化
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class ArchiveModel(Base):
__tablename__ = "archives"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(255), nullable=False)
category = Column(String(50), nullable=False)
file_type = Column(String(20), nullable=False)
file_path = Column(String(512), nullable=False)
file_size = Column(BigInteger)
department = Column(String(100))
creator = Column(String(50))
description = Column(Text)
security_level = Column(String(20), default="内部")
created_at = Column(DateTime, default=datetime.utcnow)
MinIO 初始化
minio_client = Minio(
MINIO_ENDPOINT,
access_key=MINIO_ACCESS_KEY,
secret_key=MINIO_SECRET_KEY,
secure=False 本地测试通常关闭SSL
)
确保存储桶存在
try:
if not minio_client.bucket_exists(MINIO_BUCKET_NAME):
minio_client.make_bucket(MINIO_BUCKET_NAME)
print(f"Created bucket: {MINIO_BUCKET_NAME}")
except S3Error as e:
print(f"MinIO error: {e}")
app = FastAPI(title="新闻出版档案管理系统")
@app.post("/api/upload")
async def upload_archive(
file: UploadFile = File(...),
title: str = Form(...),
category: str = Form(...),
department: str = Form(...),
creator: str = Form(...),
description: str = Form(""),
security_level: str = Form("内部")
):
"""
档案上传与归档接口
"""
1. 验证文件类型
allowed_extensions = {'pdf', 'tiff', 'jpg', 'png', 'mp4', 'wav'}
file_ext = file.filename.split('.')[-1].lower()
if file_ext not in allowed_extensions:
raise HTTPException(status_code=400, detail="不支持的文件格式")
2. 生成唯一文件名
file_id = str(uuid.uuid4())
object_name = f"{category}/{datetime.now().strftime('%Y%m')}/{file_id}.{file_ext}"
3. 读取文件内容并上传至MinIO
try:
file_content = await file.read()
file_size = len(file_content)
minio_client.put_object(
MINIO_BUCKET_NAME,
object_name,
data=io.BytesIO(file_content),
length=file_size,
content_type=file.content_type
)
except S3Error as e:
raise HTTPException(status_code=500, detail=f"文件存储失败: {str(e)}")
4. 写入元数据到PostgreSQL
db = SessionLocal()
try:
db_archive = ArchiveModel(
title=title,
category=category,
file_type=file_ext,
file_path=object_name,
file_size=file_size,
department=department,
creator=creator,
description=description,
security_level=security_level
)
db.add(db_archive)
db.commit()
db.refresh(db_archive)
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=f"数据库写入失败: {str(e)}")
finally:
db.close()
return {"code": 200, "message": "归档成功", "data": {"id": db_archive.id, "file_id": file_id}}
@app.get("/api/list")
async def list_archives(category: str = None, limit: int = 10):
"""
档案列表查询接口
"""
db = SessionLocal()
try:
query = db.query(ArchiveModel)
if category:
query = query.filter(ArchiveModel.category == category)
results = query.order_by(ArchiveModel.created_at.desc()).limit(limit).all()
data = []
for item in results:
data.append({
"id": item.id,
"title": item.title,
"category": item.category,
"department": item.department,
"created_at": item.created_at.strftime("%Y-%m-%d %H:%M:%S"),
"file_size": item.file_size
})
return {"code": 200, "data": data}
finally:
db.close()
if __name__ == "__main__":
import uvicorn
补充import io
import io
uvicorn.run(app, host="0.0.0.0", port=8000)
```

注意:代码中使用了io模块处理文件流,请确保在main.py头部已添加import io。启动服务:
```bash
python main.py
```
五、实操操作与接口测试
系统启动后,我们可以使用Postman或curl命令进行测试。这里以curl命令演示如何上传一份“报纸期刊”类的PDF档案。
1. 准备一个测试文件test_news.pdf。
2. 在终端执行以下命令:
```bash
curl -X POST "http://127.0.0.1:8000/api/upload" \
-F "file=@test_news.pdf" \
-F "title=2023年10月号日报归档" \
-F "category=报纸期刊" \
-F "department=编辑部" \
-F "creator=张三" \
-F "description=重要新闻报道存档" \
-F "security_level=内部"
```
如果返回结果包含"code": 200,说明文件已成功存入MinIO,且元数据已写入PostgreSQL。
3. 查询归档列表:
```bash
curl "http://127.0.0.1:8000/api/list?category=报纸期刊&limit=5"
```
六、制度合规性技术保障
为了符合新闻出版档案制度关于长期保存和不可篡改的要求,我们需要在MinIO上开启对象锁定(Object Locking)功能。修改docker-compose.yml中的MinIO命令:
```yaml
command: server /data --console-address ":9001" --address ":9000"
```
并在MinIO控制台中,对news-archives存储桶开启“Versioning”和“Object Lock”。设置WORM(Write Once Read Many)模式,确保一旦归档的文件在保留期内无法被删除或覆盖,从而满足档案法关于原始记录性的硬性要求。