4. 测试环境搭建:自己动手,丰衣足食
环境搭建这事儿,说大不大,说小不小。我见过太多人倒在这一步——要么依赖别人搭好的测试环境,一有问题就抓瞎;要么照着文档敲半天命令,结果跑不起来。说白了,环境搭建是个熟练活,自己亲手搭一遍,后面调试问题会快很多。
这一章咱们就干两件事:先在本机搭一个轻量的本地测试服务,能快速验证接口行为;再用 Docker 把整个密文检索服务跑起来,保证团队之间的环境一致。你想想看,开发机和服务器的环境差别,往往是 bug 的温床。Docker 就是来解决这个问题的。
4.1 目录结构,先有个概念
动手之前,我们先看一眼最终的目录长什么样。我个人习惯按功能分层,这样代码和配置不会乱。下面这张图就是本章要搭建的完整结构。
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
如果返回之前那样,那就说明部署成功了。
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,或者改端口。
好了,环境搭建就到这里。你现在应该拥有一个可以运行的本地服务和一个 Docker 容器。后续所有章节的接口测试、脚本编写、CI 集成,都会依赖这个环境。我的习惯是:把 docker-compose up 和 python app.py 两个命令贴在开发文档的头,这样新同事三分钟就能跑起来。