4. 测试环境搭建:自己动手,丰衣足食

环境搭建这事儿,说大不大,说小不小。我见过太多人倒在这一步——要么依赖别人搭好的测试环境,一有问题就抓瞎;要么照着文档敲半天命令,结果跑不起来。说白了,环境搭建是个熟练活,自己亲手搭一遍,后面调试问题会快很多。

这一章咱们就干两件事:先在本机搭一个轻量的本地测试服务,能快速验证接口行为;再用 Docker 把整个密文检索服务跑起来,保证团队之间的环境一致。你想想看,开发机和服务器的环境差别,往往是 bug 的温床。Docker 就是来解决这个问题的。

4.1 目录结构,先有个概念

动手之前,我们先看一眼最终的目录长什么样。我个人习惯按功能分层,这样代码和配置不会乱。下面这张图就是本章要搭建的完整结构。

测试环境搭建知识结构 本地测试服务 Flask 后端 SQLite 数据库 测试数据 + 索引 Docker 部署 Dockerfile docker-compose.yml 密文检索镜像 统一测试接口 可复用的 CI/CD 环境 两种方式最终都暴露相同的 REST API,测试脚本可以自由切换目标 base_url

4.2 搭建本地测试服务(Flask + SQLite)

我们为什么先搞本地服务?原因很简单:快速迭代。你写一个接口,想测一下逻辑对不对,没必要启动 Docker,在本地用 python 跑起来就行。密文检索的核心逻辑包括数据加密、索引构建、检索匹配,本地跑一遍就能把大部分 bug 抓出来。

我项目中曾经吃过一次亏:直接在 Docker 里写代码,每次改完都要重新构建镜像,光等 build 就花了好几分钟。后来学乖了,本地开发测试,Docker 只负责最终集成验证。

4.2.1 环境准备

  • Python 3.9+(建议用 3.10,我踩过 pycryptodome 库在 3.11 的坑)
  • pip 安装依赖:flask、flask-cors、pycryptodome、sqlite3(标准库自带)
  • 建议先创建一个虚拟环境:python -m venv venv

4.2.2 项目骨架

目录结构长这样,我们一行一行来搭:

local_test/
├── app.py                  # Flask 入口
├── crypto_engine.py        # 加密/解密/构建索引
├── db.py                   # SQLite 操作封装
├── test_data.json          # 测试明文数据
└── requirements.txt

app.py 是最简的 Flask 应用,暴露两个端点:/api/search 和 /api/index/build。你看核心代码:

from flask import Flask, request, jsonify
from crypto_engine import CryptoEngine
from db import Database

app = Flask(__name__)
db = Database('cipher_search.db')
engine = CryptoEngine()

@app.route('/api/index/build', methods=['POST'])
def build_index():
    """从 test_data.json 加载数据,构建密文索引"""
    engine.build_index(db)
    return jsonify({'status': 'ok', 'message': '索引构建完成'})

@app.route('/api/search', methods=['POST'])
def search():
    keyword = request.json.get('keyword', '')
    # 对关键词加密,去密文索引中匹配
    encrypted_keyword = engine.encrypt(keyword)
    results = db.search_index(encrypted_keyword)
    return jsonify({'results': results})

注意:这里的加密是确定性加密——同一个明文每次加密结果相同,才能做等值检索。如果你用 AES-CBC 加随机 IV,那密文每次都不一样,检索就废了。

4.2.3 启动并测试

终端里执行 python app.py,服务默认跑在 http://127.0.0.1:5000。你可以用 curl 或者 Postman 验证:

# 构建索引
curl -X POST http://127.0.0.1:5000/api/index/build

# 检索
curl -X POST http://127.0.0.1:5000/api/search \
  -H "Content-Type: application/json" \
  -d '{"keyword": "机密文件"}'

返回结果如果是一个 JSON 数组,那就说明环境跑通了。我第一次搭的时候卡在了 CORS 跨域上,前端调半天报跨域错误,后来加了 flask-cors 就解决了。嗯,这里提个醒:如果你用前端直接调,记得注册 CORS。

4.3 使用 Docker 部署密文检索服务

本地服务搭好了,接下来说说 Docker。为什么要用 Docker?我遇到最头疼的场景就是:同事说“我本地能跑啊”,但一上服务器就崩。原因无非是系统库版本、Python 路径、数据库文件权限不一样。Docker 把整个环境打包成镜像,跑起来就是复制品,完美解决“到我这就坏了”的问题。

4.3.1 编写 Dockerfile

在项目根目录创建 Dockerfile:

FROM python:3.10-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

注意几点:

  • 镜像选 slim 版,体积小很多。我亲测 full 版大了 500MB,没必要。
  • WORKDIR /app 让容器内文件都在 /app 下,避免路径混乱。
  • 暴露 5000 端口,和 Flask 一致。

4.3.2 使用 docker-compose 编排

虽然只有一个服务,但我建议还是用 docker-compose。为啥?以后要加数据库、缓存或者认证组件,直接加 service 就行。而且 compose 文件可以配置挂载卷,让测试数据持久化。

version: '3.8'
services:
  cipher-search:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - ./test_data.json:/app/test_data.json
      - ./cipher_search.db:/app/cipher_search.db
    environment:
      - FLASK_ENV=development

启动命令很简单:

docker-compose up --build

4.3.3 验证容器内的服务

容器跑起来后,在宿主机访问 http://localhost:5000 就能调接口了。我习惯先跑一个简单的健康检查:

curl http://localhost:5000/api/index/build

如果返回之前那样,那就说明部署成功了。

小贴士: 如果你在 Windows/Mac 上用 Docker,注意文件挂载的权限。test_data.json 在容器内可能变成只读,构建索引时如果尝试写入,会报 PermissionError。解决方案:在 Dockerfile 中添加 RUN chmod 777 /app,或者把数据文件挂载到可写目录。

4.4 两种方式,一张对比表

你可能要问:到底用哪个?我的建议是:开发调试用本地,集成测试和 CI 用 Docker。下面这个表方便你决策:

特性 本地测试服务 Docker 部署
启动速度 秒级 首次构建较慢,后续秒级
环境一致性 依赖本机配置 完全一致
调试便利性 可以直接 print / 断点 需要 docker logs 或 attach
适合场景 日常开发、单测 CI/CD、演示、生产预演

4.5 避坑指南(我替你踩过的)

  • 本地 Python 版本不一致: 我之前项目用 3.10,同事的是 3.8,结果 f-string 语法报错。用 Docker 后这个问题就消失了。
    不过如果你坚持本地跑,建议用 pyenv 管理版本。
  • SQLite 文件锁问题: 本地跑多个 flask worker 时,可能同时写数据库,出现 database is locked。解决方案:临时改成单 worker,或者用 SQLite WAL 模式。我一般测试时只开单进程。
  • 加密种子(seed)不一致: 如果本地和 Docker 用了不同的随机种子生成密钥,密文检索结果会完全不同。
    务必在 crypto_engine.py 中固定一个密钥(测试用),比如 SECRET_KEY = b'16byte_test_key!'。正式环境当然不能这么干,但测试环境要可重复。
  • 端口占用: 本地 5000 端口可能被 AirPlay 或其他程序占用(Mac 用户懂的)。启动前检查一下:lsof -i :5000,或者改端口。
重要警告: 本章搭建的测试环境使用了硬编码的加密密钥,仅供功能验证。不要把这个密钥直接用到生产环境!我们会在第 18 章专门讲密钥管理和安全审计。

好了,环境搭建就到这里。你现在应该拥有一个可以运行的本地服务和一个 Docker 容器。后续所有章节的接口测试、脚本编写、CI 集成,都会依赖这个环境。我的习惯是:把 docker-compose up 和 python app.py 两个命令贴在开发文档的头,这样新同事三分钟就能跑起来。