Flask+Heroku部署的URL短链工具偶发‘URL未找到’问题求助
问题根源分析
你的猜测完全正确,核心问题就是内存字典shortened_urls的临时存储特性,加上部署环境的特殊机制导致的:
- Heroku的Dyno是无状态的,会定期重启、自动缩放或者在闲置时休眠,每次重启后内存中的所有数据都会被清空,所以之前生成的短链映射会丢失。
- 你写的从
urls.json加载数据的逻辑只在if __name__ == "__main__"分支里执行,但Heroku部署Flask应用时,通常用Gunicorn等WSGI服务器启动,不会触发这个分支,所以即使有持久化的json文件,启动时也不会加载。 - 另外你注释了写入
urls.json的代码,就算本地运行也没把数据持久化到文件,部署后更不可能保留数据。
Homebrew安装Heroku CLI的警告和这个问题无关,可以忽略。
解决方案
方案1:临时修复(用文件持久化,仅用于测试)
先修复文件读写的逻辑,让数据能正确持久化到文件,并且启动时自动加载:
修改后的main.py代码:
import random import string import json import os from flask import Flask, render_template, redirect, request app = Flask(__name__) # 定义json文件路径 URLS_FILE = "urls.json" shortened_urls = {} def load_urls(): """启动时加载已保存的短链数据""" if os.path.exists(URLS_FILE): with open(URLS_FILE, "r") as f: return json.load(f) return {} def save_urls(): """保存当前短链数据到文件""" with open(URLS_FILE, "w") as f: json.dump(shortened_urls, f) def generate_short_url(length=6): chars = string.ascii_letters + string.digits short_url = "".join(random.choice(chars) for _ in range(length)) return short_url # 启动时加载数据 shortened_urls = load_urls() @app.route("/", methods=["GET", "POST"]) def index(): if request.method == "POST": long_url = request.form['long_url'] short_url = generate_short_url() while short_url in shortened_urls: short_url = generate_short_url() shortened_urls[short_url] = long_url save_urls() # 新增链接后立即保存 full_short_url = request.url_root + short_url return render_template("index.html", short_url=full_short_url) return render_template("index.html") @app.route("/<short_url>") def redirect_url(short_url): long_url = shortened_urls.get(short_url) if long_url: return redirect(long_url) else: return "URL not found", 404 if __name__ == "__main__": app.run(debug=True)
⚠️ 注意:Heroku的文件系统是临时的,每次Dyno重启后,urls.json会被重置为部署时的状态,所以这个方案只能用于临时测试,不能作为生产环境的解决方案。
方案2:生产环境推荐(用数据库持久化)
Heroku提供免费的PostgreSQL数据库服务,用SQLAlchemy来连接数据库,实现真正的持久化存储:
步骤1:安装依赖
在requirements.txt中添加:
flask flask-sqlalchemy psycopg2-binary gunicorn
步骤2:修改main.py使用SQLAlchemy
import random import string from flask import Flask, render_template, redirect, request from flask_sqlalchemy import SQLAlchemy import os app = Flask(__name__) # 配置数据库,Heroku会自动设置DATABASE_URL环境变量 app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///urls.db') app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app) # 定义URL映射模型 class URLMapping(db.Model): id = db.Column(db.Integer, primary_key=True) short_url = db.Column(db.String(6), unique=True, nullable=False) long_url = db.Column(db.String(500), nullable=False) def generate_short_url(length=6): chars = string.ascii_letters + string.digits short_url = "".join(random.choice(chars) for _ in range(length)) # 检查是否已存在 while URLMapping.query.filter_by(short_url=short_url).first(): short_url = generate_short_url() return short_url # 创建数据库表(首次运行时需要) with app.app_context(): db.create_all() @app.route("/", methods=["GET", "POST"]) def index(): if request.method == "POST": long_url = request.form['long_url'] short_url = generate_short_url() # 保存到数据库 new_url = URLMapping(short_url=short_url, long_url=long_url) db.session.add(new_url) db.session.commit() full_short_url = request.url_root + short_url return render_template("index.html", short_url=full_short_url) return render_template("index.html") @app.route("/<short_url>") def redirect_url(short_url): url_entry = URLMapping.query.filter_by(short_url=short_url).first() if url_entry: return redirect(url_entry.long_url) else: return "URL not found", 404 if __name__ == "__main__": app.run(debug=True)
步骤3:部署到Heroku
- 确保你已经登录Heroku CLI,创建应用并关联数据库:
heroku create your-app-name heroku addons:create heroku-postgresql:hobby-dev
- 提交代码并部署:
git add . git commit -m "Switch to PostgreSQL for persistent storage" git push heroku main
这样修改后,短链数据会持久化到PostgreSQL数据库,即使Dyno重启,数据也不会丢失,彻底解决问题。
内容的提问来源于stack exchange,提问作者nc__33
相关产品推荐
相关产品推荐

