解决数字档案馆定制难:手把手教你搭建微服务扩展网关
一、方案背景与架构设计
现有数字档案馆系统多为单体架构,核心代码封闭,修改风险高。为了在不修改原有系统代码的前提下实现业务逻辑的定制化扩展,我们将采用反向代理+插件化微服务的架构。该方案在用户与旧系统之间构建一个中间层,拦截特定请求,执行自定义逻辑(如数据清洗、字段补全、流程控制),再转发给原系统。这能实现“零侵入”的定制化改造。
本指南将使用 Python 的 FastAPI 框架构建一个高性能的扩展网关,演示如何拦截档案上传接口,自动添加“密级”字段,解决旧系统无法自动识别文件密级的问题。
二、环境准备与项目初始化
在开始编码前,需要准备基础的运行环境。请确保你的操作系统已安装 Python 3.9 或更高版本。
1. 安装系统依赖
打开终端,执行以下命令安装项目所需的 Python 库。我们使用 FastAPI 作为 Web 框架,httpx 用于异步转发请求,pydantic 用于数据校验。
```bash pip install fastapi uvicorn httpx pydantic python-multipart ```2. 创建项目目录结构
在服务器工作目录下执行以下命令创建标准的项目文件夹结构:
```bash mkdir -p archive-extension-gateway/app/plugins cd archive-extension-gateway touch app/main.py app/custom_logic.py requirements.txt Dockerfile docker-compose.yml ```三、编写核心扩展网关代码
这一步是核心,我们将编写能够拦截请求并修改数据的代码。
1. 定义依赖文件
为了确保部署环境一致,请将以下内容完整写入 requirements.txt 文件:
```text fastapi==0.104.1 uvicorn==0.24.0 httpx==0.25.0 pydantic==2.5.0 python-multipart==0.0.6 ```2. 编写业务定制逻辑插件
在 app/custom_logic.py 中写入具体业务逻辑。这里模拟一个场景:旧系统接收档案数据时,如果缺少“security_level”字段,会报错。我们的插件将根据文件名自动补全该字段。
```python import re from typing import Dict, Any def process_archive_metadata(payload: Dict[str, Any]) -> Dict[str, Any]: """ 定制化逻辑:处理档案元数据 如果数据中缺少 security_level,根据文件名规则自动推断 """ file_name = payload.get("file_name", "") 逻辑:如果文件名包含 [SECRET],则自动补全密级为 3 if "security_level" not in payload: if "[SECRET]" in file_name.upper(): payload["security_level"] = 3 print(f"[插件执行] 检测到涉密文件,已自动设置密级为3: {file_name}") else: payload["security_level"] = 1 默认公开 逻辑:强制添加自定义归档来源字段 payload["archive_source"] = "定制化扩展网关自动注入" return payload ```3. 编写主代理服务
在 app/main.py 中构建网关服务。该服务监听 8000 端口,将请求转发至假设的旧系统(假设运行在 8080 端口)。注意:此处实现了完整的请求拦截、修改、转发闭环。
```python import fastapi from fastapi import Request, Response import httpx import json from app.custom_logic import process_archive_metadata app = fastapi.FastAPI() 配置原系统的地址 LEGACY_SYSTEM_URL = "http://legacy-system-backend:8080" @app.post("/api/v1/archive/upload") async def custom_archive_upload(request: Request): """ 拦截档案上传接口,执行定制逻辑后转发 """ 1. 接收前端发来的原始数据 form_data = await request.form() 将FormData转换为字典以便处理(实际生产中需处理文件流对象,此处简化演示字段处理) payload_dict = {} file_obj = None for key, value in form_data.items(): if hasattr(value, "file"): 判断是否为文件对象 file_obj = value payload_dict["file_name"] = value.filename else: 尝试解析JSON字符串字段 try: payload_dict[key] = json.loads(value) except: payload_dict[key] = value print(f"[网关日志] 收到原始数据: {payload_dict}") 2. 执行定制化插件业务逻辑 processed_data = process_archive_metadata(payload_dict) print(f"[网关日志] 处理后数据: {processed_data}") 3. 构造转发给旧系统的数据包 files = {"file": (file_obj.filename, await file_obj.read(), file_obj.content_type)} if file_obj else None data = {k: (json.dumps(v) if isinstance(v, (dict, list)) else str(v)) for k, v in processed_data.items() if k != "file_name"} 4. 异步转发请求至原系统 async with httpx.AsyncClient(timeout=30.0) as client: try: response = await client.post( f"{LEGACY_SYSTEM_URL}/api/v1/archive/upload", files=files, data=data ) return Response( content=response.content, status_code=response.status_code, headers=dict(response.headers) ) except Exception as e: return {"status": "error", "message": f"转发请求失败: {str(e)}"} @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"]) async def proxy_all(request: Request, path: str): """ 通用代理:对于未定制拦截的请求,直接透传转发 """ url = f"{LEGACY_SYSTEM_URL}/{path}" method = request.method headers = dict(request.headers) 过滤掉Host头,避免转发冲突 headers.pop("host", None) body = await request.body() async with httpx.AsyncClient(timeout=30.0) as client: response = await client.request( method, url, headers=headers, content=body, params=request.query_params ) return Response( content=response.content, status_code=response.status_code, headers=dict(response.headers) ) ```四、容器化部署配置

为了实现一键部署和隔离环境,我们将使用 Docker 进行封装。
1. 编写 Dockerfile
将以下配置写入 Dockerfile,构建一个轻量级的 Python 运行镜像:
```dockerfile FROM python:3.9-slim WORKDIR /app 安装系统依赖(如果需要处理复杂文件如PDF,可在此添加poppler-utils等) RUN apt-get update && apt-get install -y gcc && rm -rf /var/lib/apt/lists/ 复制依赖文件并安装 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt 复制源代码 COPY . . 暴露网关端口 EXPOSE 8000 启动命令 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] ```2. 编写 Docker Compose 编排文件
将以下配置写入 docker-compose.yml。为了方便测试,我加入了一个 Nginx 容器来模拟“原数字档案馆系统”,这样你直接运行即可看到效果。
```yaml version: '3.8' services: 模拟的原数字档案馆系统(仅用于接收请求并打印,证明数据被修改了) legacy-system-backend: image: kennethreitz/httpbin ports: - "8080:80" 我们构建的扩展网关 extension-gateway: build: . ports: - "8000:8000" environment: - LEGACY_SYSTEM_URL=http://legacy-system-backend:8080 depends_on: - legacy-system-backend ```五、运行与实操验证
完成代码编写后,按照以下步骤启动服务并进行验证。
1. 构建并启动服务
在项目根目录下执行:
```bash docker-compose up --build ```看到终端输出 "Application startup complete" 字样即表示启动成功。
2. 发送测试请求
打开一个新的终端窗口,使用 curl 命令模拟前端发送一个不包含“security_level”的档案上传请求。
```bash curl -X POST "http://localhost:8000/api/v1/archive/upload" \ -F "file=@README.md" \ -F "metadata={\"title\": \"测试档案\", \"code\": \"TEST001\"};type=application/json" ``>3. 观察日志验证结果
回到运行 docker-compose 的终端窗口,你将看到如下日志输出:
```text [网关日志] 收到原始数据: {'file_name': 'README.md', 'metadata': {'title': '测试档案', 'code': 'TEST001'}} [插件执行] 检测到普通文件,已自动设置密级为1: README.md [网关日志] 处理后数据: {'file_name': 'README.md', 'metadata': {'title': '测试档案', 'code': 'TEST001'}, 'security_level': 1, 'archive_source': '定制化扩展网关自动注入'} ```通过日志可以清晰看到,尽管客户端发送的数据中没有 security_level 和 archive_source,但网关层自动补全了这些字段。如果此时将文件名改为 SECRET_REPORT.pdf 并重试,日志将显示密级被自动设置为 3。
六、总结
本指南通过构建一个基于 FastAPI 的微服务网关,成功解决了数字档案馆系统定制化能力差的问题。核心在于利用反向代理拦截请求,在中间层通过 Python 脚本灵活注入业务逻辑,最后再将处理好的数据转发给原系统。这种方式无需触碰旧系统核心代码,极大地降低了开发风险,且可根据业务需求随时修改 custom_logic.py 中的逻辑,实现了真正的“热插拔”式定制。