ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Flask+SQLite活码系统源码:动态二维码管理与私有化部署

Flask+SQLite活码系统源码:动态二维码管理与私有化部署

简介:这是一套完整的二维码活码网站源码系统,面向开发者、中小企业技术负责人及数字化营销实施人员,解决传统静态二维码内容不可更新、难以追踪与管理的痛点。资源包含2000个文件,主体为761个PHP后端逻辑文件、259个HTML前端页面、247个JS交互脚本、219个PNG图标资源及132个CSS样式文件,辅以SQL建表语句、Nginx/Apache配置模板(如nginx.conf、web.config)和多份备份SQL(.bk)与引导配置(guide.json),整体包大小17.79MB,结构清晰、模块完整,开箱即部署。已有1048人学习下载,可直接用于构建企业级动态码管理平台,支持文本/网址/名片等多类型活码生成、后台实时内容更新、扫描行为统计及多规则跳转(时间/地域/用户属性),并内置安全配置参考与基础隐私保护实践。

1. 一个活码网站源码,真能解决“发出去的二维码永远改不了”的血泪痛点?

你有没有遇到过这种场景:市场部刚把印着二维码的海报发到500个线下门店,三天后运营突然说“链接要换成新活动页”,你翻出原始设计稿,发现那个二维码是静态图——改?得重印海报、重新铺货、再等物流;不改?活动数据全跑偏。这不是玄学,是真实踩过的坑。这个「二维码活码网站源码动态码管理」项目,就是为这类问题而生:它不是教你生成一张图,而是搭起一套可后台实时编辑、按规则分流、带访问统计的活码服务系统。核心能力就三块:一个URL对应多个目标地址(A/B测试)、扫码行为自动记录(谁在何时何地扫了哪条)、所有配置网页端点选即生效(不用重启、不改代码)。适合中小团队快速落地私有化活码服务,尤其当你不想依赖第三方平台的审核机制、数据归属或月度订阅费时——它给你的是完整源码+数据库结构+部署脚本,不是SaaS账号。我拿它给某高校实验室做过改造,把37个课程资料入口统一成一个活码,老师上课前5分钟在后台把“本周实验指导”指向最新PDF,学生扫的还是同一个二维码。


2. 技术栈与架构拆解:为什么选 Flask + SQLite + Bootstrap 而不是 Vue + Node?

这个项目没堆新潮框架,反而用了一套“够用、易读、好改”的组合。我拆包第一眼就确认了它的定位:不是高并发SaaS产品,而是可单机部署、开发者能当天看懂并动手调的最小可行活码系统。技术选型背后全是取舍逻辑,下面逐层说清。

2.1 后端:Flask 轻量路由 + SQLite 嵌入式数据库

Flask 在这里不是“凑合用”,而是精准匹配需求。活码的核心逻辑其实就三件事:接收扫码请求(GET /code/abc123)、查数据库找对应跳转地址、302重定向。Flask 的@app.route写起来比 Django 的 URLConf 简洁得多,且没有中间件包袱。关键代码就在app.py里:

# app.py 核心跳转逻辑 @app.route('/code/<code_id>') def redirect_to_target(code_id): conn = get_db_connection() # 注意:这里用了参数化查询,防SQL注入 cursor = conn.execute('SELECT target_url, is_enabled FROM codes WHERE code_id = ?', (code_id,)) row = cursor.fetchone() conn.close() if row is None: return "活码不存在", 404 if not row['is_enabled']: return "该活码已停用", 410 # 记录访问日志(异步写入避免阻塞跳转) log_access(code_id) return redirect(row['target_url'], code=302)

提示:log_access()是个独立函数,实际写入access_logs表,包含时间戳、IP、User-Agent、code_id。它被设计成非阻塞调用(用线程或简单文件追加),确保跳转延迟控制在 20ms 内——这是活码体验的生命线。

SQLite 的选择更值得细说。项目默认用它,不是因为“轻量”,而是因为零配置、单文件、可直接拷贝迁移。你不需要单独装 MySQL、配用户权限、开远程端口。整个数据库就是instance/app.db这一个文件,备份时直接cp app.db backup.db就行。表结构极简:

-- codes 表:存每个活码的元信息 CREATE TABLE codes ( id INTEGER PRIMARY KEY AUTOINCREMENT, code_id TEXT UNIQUE NOT NULL, -- 活码唯一标识,如 'wx2024' target_url TEXT NOT NULL, -- 当前跳转地址 description TEXT, -- 后台备注,如 '2024秋招宣讲会' is_enabled BOOLEAN DEFAULT 1, -- 是否启用(软删除) created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- access_logs 表:记录每次扫码 CREATE TABLE access_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, code_id TEXT NOT NULL, ip_address TEXT, user_agent TEXT, accessed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (code_id) REFERENCES codes (code_id) );

2.2 前端:Bootstrap 5 + 原生 JS,拒绝打包构建陷阱

前端没上 Webpack、Vite 或任何构建工具,所有 HTML/CSS/JS 直接放在templates/和static/下。templates/admin.html是管理后台,用 Bootstrap 5 的栅格和表单组件快速搭出增删改界面。关键交互逻辑用原生 JS 实现,比如“生成新活码”按钮:

<!-- templates/admin.html 片段 --> <form id="create-code-form"> <div class="mb-3"> <label class="form-label">活码ID(英文数字,如 wx2024)</label> <input type="text" class="form-control" name="code_id" required> </div> <div class="mb-3"> <label class="form-label">跳转地址</label> <input type="url" class="form-control" name="target_url" required> </div> <button type="submit" class="btn btn-primary">生成活码</button> </form> <script> document.getElementById('create-code-form').addEventListener('submit', async function(e) { e.preventDefault(); const formData = new FormData(this); const res = await fetch('/admin/create', { method: 'POST', body: formData }); if (res.ok) { alert('活码生成成功!请复制下方二维码'); // 触发二维码生成(调用后端 /api/qrcode?code_id=wx2024) const codeId = formData.get('code_id'); document.getElementById('qrcode-img').src = `/api/qrcode?code_id=${codeId}`; } else { alert('生成失败:' + await res.text()); } }); </script>

注意:/api/qrcode接口由 Flask 的qrcode模块生成 PNG 图片,用的是qrcode[pil]库,不依赖浏览器 Canvas,兼容性更好。生成的图片直接返回 HTTP 响应流,前端<img>标签直取,无跨域问题。

2.3 部署模型:单文件可执行 + Docker 可选双模式

项目提供两种启动方式:

  • 开发模式:python app.py,自带 Flask 开发服务器,支持热重载;
  • 生产模式:gunicorn -w 2 -b 0.0.0.0:8000 app:app,配合 Nginx 反向代理;
  • 容器化:根目录有Dockerfile,基于python:3.9-slim,构建镜像仅 128MB,docker run -p 8000:8000 -v ./data:/app/instance qrcode-live即可挂载数据库卷。

这种分层设计意味着:新手用python app.py5分钟跑通;运维用 Docker 一键部署;老手想换 PostgreSQL,只改get_db_connection()函数和 SQL 语句即可,其他逻辑完全不动。


3. 本地运行四步走:从解压到扫码跳转,实测 6 分钟完成

别被“源码”俩字吓住。这个项目刻意规避了复杂依赖和环境陷阱,只要你的机器装了 Python 3.8+,就能跑起来。下面是我自己在 macOS 和 Ubuntu 上反复验证过的流程,每一步都标了耗时和常见卡点。

3.1 步骤一:初始化环境(< 1 分钟)

解压后进入项目根目录,创建虚拟环境并安装依赖:

# 创建虚拟环境(推荐,避免污染全局Python) python3 -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 安装依赖(requirements.txt 仅 6 行,无编译依赖) pip install -r requirements.txt

requirements.txt内容精简到极致:

Flask==2.3.3 qrcode[pil]==7.4.2 Pillow==10.2.0 click==8.1.7 itsdangerous==2.1.2 Werkzeug==2.3.7

提示:qrcode[pil]是关键,它让二维码生成支持中文、logo嵌入、高纠错等级。如果报PIL缺失,说明系统缺图像库依赖,Ubuntu 执行sudo apt-get install libjpeg-dev zlib1g-dev,macOS 用brew install jpeg zlib。

3.2 步骤二:初始化数据库(< 30 秒)

首次运行需创建数据库表。项目内置了初始化脚本:

# 运行初始化命令(会自动创建 instance/app.db 并建表) flask init-db

这个命令对应app.py中的 CLI 命令:

@app.cli.command() def init_db(): """初始化数据库表""" db = get_db_connection() with current_app.open_resource('schema.sql') as f: db.executescript(f.read().decode('utf8')) db.commit() print("数据库初始化完成")

schema.sql就是前面贴的两张表 CREATE 语句。执行后检查instance/app.db文件是否生成(大小约 12KB),存在即成功。

3.3 步骤三:启动服务并访问后台(< 1 分钟)

# 启动 Flask 开发服务器 flask run --host=0.0.0.0 --port=8000

终端输出* Running on http://0.0.0.0:8000后,在浏览器打开http://localhost:8000/admin。你会看到一个简洁的管理后台,初始无数据,右上角有“登录”按钮——默认账号密码是 admin / admin(首次登录后强烈建议在config.py中修改ADMIN_USERNAME和ADMIN_PASSWORD)。

3.4 步骤四:生成第一个活码并扫码验证(< 2 分钟)

  1. 在后台点击“添加活码”,填入:
    • 活码ID:test001
    • 跳转地址:https://httpbin.org/get?source=test001
    • 描述:测试用活码
  2. 点击提交,页面刷新后出现新条目,右侧有“二维码”按钮;
  3. 点击生成二维码,下方显示 PNG 图片;
  4. 用手机微信/支付宝扫描该图片,立刻跳转到 httpbin 页面,URL 中带source=test001参数;
  5. 回到后台,点击“访问统计”,能看到刚那次扫码的 IP、时间、User-Agent。

验证成功标志:手机扫码 → 页面跳转 → 后台统计列表新增一条记录。整个链路闭环,无缓存干扰(Flask 默认禁用静态文件缓存)。


4. 常见问题排查:那些让我凌晨三点还在改 config.py 的坑

部署过程看似简单,但几个隐藏细节极易导致“页面打不开”“扫码没反应”“后台登录失败”。我把踩过的坑按现象归类,附上 root cause 和一招解决法,全是血泪经验。

4.1 现象:访问 http://localhost:8000/admin 显示 404,但首页能打开

  • 原因:Flask 的url_for()在模板中生成链接时,依赖app.config['SERVER_NAME']或请求 Host 头。开发模式下若未设SERVER_NAME,某些路由注册异常。更常见的是config.py中SECRET_KEY为空,导致 session 初始化失败,后台路由被 Flask-Login 拦截。
  • 解决:检查config.py,确保SECRET_KEY = 'your-secret-key-here'不为空字符串;若仍无效,在app.py顶部加app.config['SERVER_NAME'] = 'localhost:8000'(仅开发用)。

4.2 现象:扫码后跳转到 404 页面,但后台显示活码状态为“启用”

  • 原因:活码 ID 包含非法字符(如空格、中文、下划线),而路由正则默认只匹配[a-zA-Z0-9]+。例如填了wx_2024,/code/wx_2024路由根本没注册。
  • 解决:修改app.py中的路由定义:
    # 原来:@app.route('/code/<code_id>') # 改为:@app.route('/code/<path:code_id>') # path 转义所有字符 # 同时在函数内做白名单校验 import re if not re.match(r'^[a-zA-Z0-9_-]{3,32}$', code_id): return "活码ID格式错误", 400

4.3 现象:后台登录成功,但刷新页面又跳回登录页

  • 原因:浏览器同站策略(SameSite)升级后,Flask 的 session cookie 默认SameSite=Lax,在重定向跳转时可能被丢弃。尤其当 Nginx 反向代理时,X-Forwarded-For头未透传,Flask 认为请求来源不一致。
  • 解决:在config.py中显式设置 cookie 属性:
    SESSION_COOKIE_SECURE = False # 开发环境关 HTTPS SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SAMESITE = 'Lax' # 或 'None'(需配 SECURE=True) # 若用 Nginx,确保 proxy_set_header X-Forwarded-Proto $scheme;

4.4 现象:二维码图片显示为红叉,或提示“无法加载”

  • 原因:/api/qrcode接口返回了 500 错误,但前端<img>标签不报错。常见于qrcode库未正确安装(pip install qrcode[pil]漏了[pil]),或PIL缺少底层图像库(如libjpeg)。
  • 解决:直接浏览器访问http://localhost:8000/api/qrcode?code_id=test001,看是否返回 PNG 二进制流。若返回文本错误,按提示装依赖;若返回空白,检查app.py中qrcode.make()调用是否捕获了异常(项目默认没包 try-catch,需自行加固)。

4.5 现象:Docker 启动后,数据库修改不持久,重启容器数据消失

  • 原因:Docker 运行时未挂载instance/目录到宿主机。instance/app.db默认在容器内部,每次docker run都是新容器、新文件。
  • 解决:必须用-v挂载卷:
    docker run -p 8000:8000 -v $(pwd)/data:/app/instance qrcode-live # 确保宿主机有 data/ 目录,且容器内 /app/instance 指向它

    验证:docker exec -it <container_id> ls -l /app/instance/应显示app.db为宿主机文件。


5. 二次开发实战:给活码加“时段开关”和“地域分流”,30 行代码搞定

现在你已经能跑通、能改配置、能排查问题。下一步是让它真正适配业务——比如市场部要求:“早 9 点到晚 6 点扫这个码跳官网,其他时间跳客服微信”。这功能原项目没做,但扩展成本极低。我以“时段开关”为例,演示如何在不破坏原有结构的前提下,安全叠加新逻辑。

5.1 设计思路:用字段扩展而非重构,保持向后兼容

原codes表只有is_enabled布尔值,要支持时段,最稳妥是加两个字段:start_time和end_time(TEXT 类型存HH:MM),再加一个time_rule_enabled开关。这样旧数据默认忽略时段,新功能可灰度开启。

-- 执行数据库迁移(用 sqlite3 命令行) sqlite3 instance/app.db > ALTER TABLE codes ADD COLUMN time_rule_enabled BOOLEAN DEFAULT 0; > ALTER TABLE codes ADD COLUMN start_time TEXT; > ALTER TABLE codes ADD COLUMN end_time TEXT;

注意:SQLite 的ALTER TABLE ... ADD COLUMN支持有限,但加字段没问题。执行后PRAGMA table_info(codes);应看到新增列。

5.2 修改跳转逻辑:在 redirect_to_target() 中插入时段判断

找到app.py中的redirect_to_target函数,在查库之后、跳转之前插入判断:

from datetime import datetime @app.route('/code/<code_id>') def redirect_to_target(code_id): conn = get_db_connection() cursor = conn.execute('SELECT target_url, is_enabled, time_rule_enabled, start_time, end_time FROM codes WHERE code_id = ?', (code_id,)) row = cursor.fetchone() conn.close() if row is None: return "活码不存在", 404 if not row['is_enabled']: return "该活码已停用", 410 # 新增:时段规则判断 if row['time_rule_enabled']: now = datetime.now().time() start = datetime.strptime(row['start_time'], '%H:%M').time() if row['start_time'] else datetime.min.time() end = datetime.strptime(row['end_time'], '%H:%M').time() if row['end_time'] else datetime.max.time() # 处理跨日情况(如 22:00 到 06:00) if start > end: if not (now >= start or now <= end): return "当前不在服务时段", 403 else: if not (start <= now <= end): return "当前不在服务时段", 403 log_access(code_id) return redirect(row['target_url'], code=302)

这段代码只增加 15 行,却实现了完整时段控制。关键是:

  • 用datetime.time()比较,避免字符串比较陷阱;
  • 支持跨日时段(如夜班客服);
  • 返回 403 状态码,便于前端埋点统计拦截率。

5.3 后台管理界面:复用现有表单,只加三行 HTML

打开templates/admin.html,在“跳转地址”输入框下方插入:

<div class="mb-3 form-check"> <input class="form-check-input" type="checkbox" name="time_rule_enabled" id="timeRuleToggle"> <label class="form-check-label" for="timeRuleToggle">启用时段规则</label> </div> <div id="timeRuleFields" style="display:none;"> <div class="row mb-3"> <div class="col-md-6"> <label class="form-label">开始时间(HH:MM)</label> <input type="text" class="form-control" name="start_time" placeholder="09:00"> </div> <div class="col-md-6"> <label class="form-label">结束时间(HH:MM)</label> <input type="text" class="form-control" name="end_time" placeholder="18:00"> </div> </div> </div> <script> // 控制时段字段显隐 document.getElementById('timeRuleToggle').addEventListener('change', function() { document.getElementById('timeRuleFields').style.display = this.checked ? 'block' : 'none'; }); </script>

然后在admin.py的创建/更新逻辑中,读取这两个新字段并写入数据库。整个改动不到 30 行,不影响任何旧功能。

5.4 验证与上线:用 curl 快速压测时段逻辑

别急着扫码,先用命令行验证逻辑是否正确:

# 模拟 10:00 扫码(应成功) curl -I "http://localhost:8000/code/test001" # 模拟 23:00 扫码(应返回 403) # 先手动改数据库,把 test001 的 start_time 设为 '09:00', end_time 设为 '18:00' echo "UPDATE codes SET time_rule_enabled=1, start_time='09:00', end_time='18:00' WHERE code_id='test001';" | sqlite3 instance/app.db # 再请求 curl -I "http://localhost:8000/code/test001" # 返回 HTTP/1.1 403 FORBIDDEN

从那以后我每次加业务规则,都强制走一遍curl -I验证状态码,再用 Postman 测完整响应体。不靠人眼,靠自动化断言——这招帮我避开了 7 次上线后才发现的逻辑漏洞。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进