目录

见微·投票系统(完整版)

基于《见微·投票系统_需求分析报告》与《见微·投票系统_系统设计文档》实现的可运行系统。

技术栈为落地实现:Python + FastAPI + SQLite(设计文档原定 Java + Spring Cloud + 达梦 + 密码机)。 本实现以「四个独立 SQLite 文件」模拟三库物理隔离,以 真国密 gmssl(SM2/SM3/SM4) 落地密码学能力, 以「密码机抽象层」模拟密钥托管,完整保留需求与设计的业务语义(匿名机制、状态机、规则引擎、审计哈希链等)。

完整版升级点(相对演示版)

能力 实现
真国密 gmssl SM2 签名/验签(GB/T 32918)、SM3 杂凑(GB/T 32905)、SM4-CBC 加密(GB/T 32907)
密码机抽象层 SM2 密钥由 app/core/hsm.py 托管(私钥持久化 data/keys/,密钥不出机概念),接口 sign/verify/public_key/fingerprint
无状态会话 SM2 签名 JWT(alg: SM2),服务端不保存会话、重启不丢会话;logout 用「吊销黑名单」按签名指纹即时失效
存储抽象层 Database 抽象 + SQLite 默认实现 + PostgreSQL/MySQL 方言预留(?%s 占位符转换),.env 一键切换 DB_BACKEND
配置管理 .env 零依赖加载 + EVOTING_* 环境变量 + 生产安全默认(验证码答案默认不出响应)
Docker 部署 Dockerfile + docker-compose.yml,数据卷持久化 data/,生产环境变量
前端完整 UI 六大模块全可视化:多议题建会、候选人两级审查与异议、名册详情、码池统计、用户/组织管理、审计筛选与哈希链校验、三套皮肤
登录页品牌化 著作人信息与副标题在线可改(GET/POST /api/v1/branding,管理员密码门禁、长度校验、变更入审计),前端彩蛋/双击编辑

一、快速开始

方式 A:本地运行

# 1. 创建虚拟环境并安装依赖
python -m venv venv
venv/Scripts/pip install -r requirements.txt        # Windows
# venv/bin/pip install -r requirements.txt          # Linux/macOS

# 2. 启动(首次自动初始化数据库并生成演示数据)
python run.py

方式 B:Docker 部署

cp .env.example .env          # 按需修改 MASTER_KEY 等生产配置
docker compose up -d --build

访问 http://127.0.0.1:8000

二、演示账号

角色 用户名 密码
系统管理员 admin 123
选举组织者 organizer 123
候选人管理员 candadmin 123
监票人 supervisor / supervisor2 123
计票人 counter 123
投票人 voter1 ~ voter10 123

三、演示流程(核心匿名投票闭环)

系统内置 4 个演示会议(均从「筹备中」开始,可完整走一遍):

会议 类型 议题
2026年度党委委员换届选举 党内选举 1 个选举议题(差额 5 选 3)
2026年度职工代表大会 内部投票 2 个议题:报告表决 + 工会委员选举(多议题)
2026年度中层干部民主评议 民主评议 测评打分(按候选人平均分 + 分布)
关于机关作风建设的意见征询 意见征询 开放式意见(自由文本)

通用流程:

  1. 组织者 登录(需图形验证码)→ 进入会议 → 候选人全部审查通过 → 发布公示 → 公示期满锁定
  2. 建立投票人名册(勾选投票人)→ 生成投票码池 → 启动投票
  3. 投票人(voter1~voter5)登录 → 领取投票码 → 逐议题作答(选举勾选 / 表决选择 / 打分 / 填写意见)→ 输入验证码提交选票
  4. 组织者 截止投票 → 计票人 发起开票计票 → 监票人(两名)确认 → 组织者 发布结果
  5. 任何人可查看结果并「验证结果签名」(真 SM2 验签);管理员/监票人可查看审计日志并「校验哈希链」

四、已实现能力(对照设计文档)

设计文档章节 实现
§4.1 匿名投票机制 随机码池 + 三库隔离(码池/选票无身份字段,选票无码、无精确时间戳)
§4.2 投票码生命周期 CSPRNG 生成、SM3 入库、原子 CAS 作废、幂等回执
§4.3 选举状态机 筹备→公示→待投票→投票中→已截止→计票中→待确认→已发布→已归档(非法跳转拒绝)
§4.4 计票引擎 可配置规则引擎(差额校验、超选无效、过半数当选、平票另行选举、多口径统计;当选分母可配 electCondition.denominatorACTUAL 实到(默认)/VALID 有效票)
§4.4 多题型计票 选举/评优(当选判定)· 表决(选项汇总)· 测评打分(平均分+分布)· 意见征询(选项+开放式意见),多议题并行一次提交
§4.5 防刷票 投票码一次性作废、幂等提交、图形验证码(算术 SVG,一次性/TTL)、IP 频控(滑动窗口 429)
实名投票 anonymity=0 时选票记录 voter_id、名册标记已投、结果标注「实名」
§5 六大模块 选举管理 / 候选人管理 / 投票管理 / 计票统计 / 监督审计 / 系统管理
§6 数据隔离 四库独立文件(system/identity/ballot/audit)
§8.2 国密 真 SM3(哈希链/选票摘要/码哈希)、真 SM4-CBC(敏感字段加密)、真 SM2 签名验签(结果防抵赖、JWT 会话)
§8.3 审计哈希链 seq 场内递增 + self_hash=SM3(prev|…) + 全链校验

五、项目结构

evoting-system/
├── run.py                    # 启动入口(初始化 + 种子 + 服务)
├── config.py                 # 配置中枢(.env 加载、DB_BACKEND、安全阈值、启动自检)
├── requirements.txt
├── Dockerfile / docker-compose.yml / .dockerignore / .env.example
├── app/
│   ├── main.py               # FastAPI 应用 + 路由挂载 + 静态前端
│   ├── core/                 # 核心机制:crypto(SM2/SM3/SM4) / hsm(密码机抽象) / ticket /
│   │                         #   rule_engine / captcha / ratelimit / errors
│   ├── db/                   # 数据层:db.py(存储抽象) + schema.py(四库 Schema)
│   ├── services/             # 业务服务(auth(JWT)/election/candidate/roster/ticket/ballot/count/audit/system)
│   ├── api/                  # REST 路由(/api/v1)
│   └── web/index.html        # 单页前端(六大模块 + 三套皮肤)
├── scripts/
│   ├── seed.py               # 演示种子数据(四类会议)
│   └── test_http.py          # HTTP 层端到端测试(含验证码,自动反解 SVG 算式)
└── data/                     # 运行时生成的四库文件与 SM2 密钥(gitignore)

六、运行测试

# 推荐:pytest 套件(数据目录自动隔离到临时目录,可无限次重跑)
python -m pytest tests/ -q

# 覆盖:核心匿名闭环 / 匿名性去关联断言 / 状态机失败重试 / 多语句原子性 /
#       备份一致性 / 名册幂等 / 候选人状态校验 / 错误契约 / 多题型计票 /
#       验证码与频控 / HTTP 端到端(TestClient 免启动服务,自动解析 SVG 算式)

旧版冒烟脚本 scripts/test_http.py 依赖 data/ 初始状态与运行中的服务(不可重跑),HTTP 层覆盖已由 tests/ 取代,保留仅作参考。

七、模型设置与模型中心(本地大模型)

管理员登录后在「系统管理」页底部「模型设置」卡片中,配置右下角智能助手的连接方式与模型。

连接方式(4 种)

方式 说明 需配置
内置 llama-server 默认。下载/导入的 GGUF 由内置引擎推理(需自行放置 llama-server 二进制,见 DEPLOY.md)
自动发现(推荐) 自动使用本机 Ollama(127.0.0.1:11434)
自定义 Ollama 服务 连接内网另一台机器上的 Ollama(内网集中推理) 服务地址
OpenAI 兼容服务 LM Studio / vLLM / llama.cpp server 等 服务地址 + 模型名

安全约束:仅支持本机/内网地址,数据不出机。 保存配置与「测试连接」都会校验地址 必须解析到回环/私网网段(127.0.0.0/8、10/8、172.16/12、192.168/16、::1、fc00::/7 等), 公网地址直接拒绝(400「仅支持本机/内网地址…数据不出机」),模型推理请求不会发往外网。 「测试连接」按所选方式探测服务并列出可用模型(Ollama /api/tags、OpenAI 兼容 /v1/models)。

模型中心(预置下载 / 导入 / 导出 / Ollama 列表)

  • 预置模型下载:预置 4 个 GGUF 模型(Qwen2.5-1.5B/3B/7B、Qwen3-4B)。需在内网提供 下载镜像并配置 EVOTING_MODEL_MIRROR(镜像基地址,服务端拼 models/<模型文件名> 下载)。 下载支持断点续传(HTTP Range:<文件名>.part + 断点元数据落盘,中断后自动续传) 与取消;未配置镜像时点击下载提示「未配置镜像地址 EVOTING_MODEL_MIRROR」。
  • 导入本地模型:上传 .gguf(流式分块写入 → GGUF 魔数校验 → 原子落盘); 内置引擎连接方式下导入成功后自动启用(后台线程加载,不阻塞请求)。
  • 导出:点「导出」经浏览器下载模型文件(服务端审计记录文件名与大小)。
  • Ollama 列表:Ollama 连接方式下列出 Ollama 已有模型,可「设为使用」。

八、配置项(.env / 环境变量)

变量 默认 说明
EVOTING_DB_BACKEND sqlite sqlite / postgres / mysql(PG/MySQL 为预留方言)
EVOTING_DATA_DIR ./data 四库文件与 SM2 密钥目录(测试隔离 / 独立挂载卷)
EVOTING_MASTER_KEY dev-… SM4 字段加密密钥派生(生产必改,建议密码机/KMS)
EVOTING_SESSION_TTL 1800 JWT 会话有效期(秒)
EVOTING_CAPTCHA_EXPOSE_ANSWER 0 验证码答案是否出响应(生产必须 0)
EVOTING_SEED_DEMO 1 首次启动是否注入演示数据
EVOTING_LOGIN_FAIL_LOCK 5 连续失败锁定阈值
EVOTING_RATE_LIMIT_REQUESTS 30 单 IP 窗口内最大请求
EVOTING_MODEL_MIRROR (空) 预置模型下载镜像基地址(内网,需支持 HTTP Range;未配置则不能下载预置模型)

九、说明与边界

  • 匿名性边界(如实声明,同设计文档 §4.1.4):本实现对抗「仅持久层数据(DBA 拖库)」场景。 选票只存分钟级批次窗口(无秒级时间戳)、码池发放/使用时间分钟级; 审计日志中投票人动作(领码/提交)不记录行为人身份与 IP(已投状态仅存身份库,供催投)。 码与身份、选票与身份在存储层均无关联;打印分发的映射即生即毁。
  • 单实例边界:验证码、IP 频控、logout 吊销黑名单、领码明文映射均为进程内存态, 仅支持单实例部署(uvicorn 多 worker 同样不共享);多实例需将这些状态外置(如 Redis)后另行改造。
  • 国密:SM2/SM3/SM4 均采用真实现(gmssl)。SM2 密钥由密码机抽象层托管(当前为本地软件密钥库后端), 生产环境可将 app/core/hsm.py 后端替换为真实密码机(私钥永不出机)。
  • SM4 当前采用 CBC 模式(IV 随机,密文 iv‖ciphertext),生产建议 SM4-GCM(已预留接口)。
  • 存储:默认 SQLite 零依赖开箱即跑;多语句写入已通过事务上下文保证原子性; PG/MySQL 后端为预留骨架,正式启用需完成方言适配与连接池。
  • 验证码:算术式 SVG,答案仅存内存、一次性使用;CAPTCHA_EXPOSE_ANSWER 默认 False,生产绝不开。
关于
16.5 MB
邀请码
    Gitlink(确实开源)
  • 加入我们
  • 官网邮箱:gitlink@ccf.org.cn
  • QQ群
  • QQ群
  • 公众号
  • 公众号

版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9 京公网安备 11010802047560号