简介:这是一套基于 Python、Flask 与 Editor.md 构建的在线 Markdown 编辑工具源码,面向具备 Flask 基础、希望学习或直接搭建在线编辑平台的开发者。项目整合了 Flask-SQLAlchemy、Flask-Login 与 sm.ms 图床,实现登录注册、文章编辑与文章列表三个核心页面,并支持自动保存与图片上传至图床等功能,适合作为 Web 开发练手项目或轻量级写作平台的基础框架。压缩包共 559 个文件,约 15.07MB,以 208 个 js、170 个 html、60 个 css 等前端资源为主,另有 14 个 py 后端文件、8 个 md 说明文档及图片、字体、配置等辅助文件,并附有 readme.md 教程。目前已有 447 人学习下载。通过阅读源码与目录结构,读者可掌握 Flask 项目分层组织、用户认证、数据库建模、Markdown 渲染及第三方图床对接等完整实现思路,对理解前后端协作与快速搭建同类工具具有较高参考价值。
1. 在线 Markdown 编辑工具:从压缩包到能跑起来的 Flask 应用
拿到一个压缩包,解压后看到editormd.min.css、bootstrap.min.css、flask-sqlalchemy这些文件名混在一起,很多人第一反应是「这到底是个能直接跑的项目,还是一堆前端素材」。这个资源是一个基于 Python + Flask + Editor.md 的在线 Markdown 编辑工具,后端用 Flask 和 Flask-SQLAlchemy 管数据,前端用 Editor.md 做编辑区,图片上传走 sm.ms 图床,数据库默认 SQLite。它包含登录注册、编辑、文章列表三个页面,支持自动保存和图片上传。
适合谁?对 Flask 有基本了解、想找一个能直接改的在线编辑器骨架的人。如果你连pip install和flask run都没用过,这个包会让你卡在环境配置上;但如果你写过最简单的 Flask 路由,这个项目能让你在半小时内看到一个带登录和自动保存的编辑器跑在本地。它解决的不是「Markdown 语法怎么用」,而是「怎么把 Editor.md 嵌进 Flask、怎么让编辑内容自动落库、怎么把本地图片传到图床」这三个工程问题。
2. 拆包与选型:为什么是 Flask + Editor.md + sm.ms
2.1 压缩包里的文件到底谁管谁
解压后先别急着装依赖,把文件按职责分三类,后面排错会快很多。
| 文件/目录 | 职责 | 是否可替换 |
|---|---|---|
AUTHORS | 作者信息,不影响运行 | 可忽略 |
default.cfg/self.cfg | 配置模板与个人配置 | 需按环境改 |
bootstrap.min.css/bootstrap-theme.min.css | 页面基础样式 | 可换版本 |
editormd.css/editormd.min.css | Editor.md 编辑区样式 | 不建议动 |
editormd.preview.css/editormd.preview.min.css | 预览区样式 | 不建议动 |
ambiance.css | 代码高亮主题 | 可换主题 |
readme.md | 安装教程 | 先读 |
default.cfg和self.cfg是 Flask 常见的配置分离做法:前者放默认值,后者放本机敏感值(比如数据库路径、图床 token)。如果你只改default.cfg,换台机器就会丢配置;正确做法是复制一份self.cfg再改。
2.2 为什么选 Editor.md 而不是手写 textarea
Markdown 在线编辑器的核心需求是「左边写、右边实时预览」。手写一个 textarea 加marked.js也能预览,但表格、流程图、数学公式、代码高亮这些都要自己接。Editor.md 把这些打包好了,还提供onchange回调,自动保存直接挂在这个回调上就行。
代价是 Editor.md 依赖 jQuery,且它的 CSS 和 JS 必须按顺序引入。常见翻车是只引了editormd.min.js没引editormd.min.css,编辑区能显示但工具栏错位。另一个坑是editormd.preview.css和editormd.css同时引入时顺序反了,预览区代码块背景会丢。
2.3 sm.ms 图床的接入逻辑
图片上传不是把文件存本地,而是前端选图后发给 Flask,Flask 再转发到 sm.ms 的 API,拿到返回的 URL 后插入编辑器。这样做的好处是文章里存的是外链,数据库只存 Markdown 文本,体积小。代价是依赖第三方图床可用性,且 sm.ms 需要 token 才能稳定上传。
常见做法是在self.cfg里放一个SM_MS_TOKEN,Flask 上传接口读这个值。如果你没有 token,匿名上传也能用,但频率受限,批量传图会失败。
3. 把项目跑起来:环境、配置与三个页面
3.1 环境准备与依赖安装
先确认 Python 版本。Flask-SQLAlchemy 和 Flask-Login 对 Python 3.7+ 支持稳定,建议用 3.8 到 3.10。不要用 3.12 直接跑老版本 Flask,部分依赖会编译失败。
# 创建虚拟环境,避免污染系统 Python python -m venv venv # 激活:Linux/macOS source venv/bin/activate # 激活:Windows venv\Scripts\activate # 安装核心依赖,版本按压缩包 readme 为准 pip install flask flask-sqlalchemy flask-login requests逻辑说明:venv隔离依赖,flask是 Web 框架,flask-sqlalchemy管 ORM,flask-login管会话,requests用于转发图片到 sm.ms。参数上,如果 readme 里给了requirements.txt,优先用pip install -r requirements.txt,避免版本漂移。
3.2 配置文件怎么改
打开default.cfg,通常会看到类似结构:
# default.cfg 示例结构,字段名以实际文件为准 DEBUG = True SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db' SECRET_KEY = 'change-me' SM_MS_TOKEN = ''复制一份为self.cfg,改三个地方:SECRET_KEY换成随机字符串,SQLALCHEMY_DATABASE_URI确认路径可写,SM_MS_TOKEN填你自己的 sm.ms token。DEBUG在本地调试时保持True,上线前改False。
提示:
SECRET_KEY不换会导致 Flask-Login 的会话可被伪造,本地玩无所谓,放到公网就是血泪经验。
3.3 初始化数据库与启动
# 进入项目目录后,初始化数据库表 python -c "from app import db; db.create_all()" # 启动 Flask flask run # 或 python app.py逻辑说明:db.create_all()会根据模型定义建表,SQLite 会在项目目录生成.db文件。如果报no such table,说明模型没被导入或create_all没执行。启动后访问http://127.0.0.1:5000,应该看到登录页。
三个页面的路由通常是/login、/register、/edit、/list。登录后进编辑页,Editor.md 加载完成后工具栏出现;文章列表页从数据库读当前用户的文章。
3.4 自动保存挂在哪
自动保存一般挂在 Editor.md 的onchange回调上,配合setInterval做节流。核心逻辑是:编辑内容变化后,每隔几秒把 Markdown 文本 POST 到/save接口,后端按文章 ID 更新记录。
// 编辑页自动保存逻辑示意 var editor = editormd("editor", { path: "/static/editormd/lib/", onchange: function() { // 内容变化时标记为未保存 window.unsaved = true; } }); // 每 5 秒检查一次,有变化才提交 setInterval(function() { if (window.unsaved) { $.post("/save", { id: articleId, content: editor.getValue() }, function(res) { window.unsaved = false; }); } }, 5000);参数说明:path必须指向 Editor.md 的lib目录,否则依赖的 marked、prettify 加载不到;5000是节流间隔,太小会增加请求,太大丢内容风险高。后端/save接口要用flask-login的@login_required保护,并校验文章归属,否则任何人改 ID 就能改别人文章。
4. 图片上传与 sm.ms 图床对接:从选图到插入链接
4.1 前端上传入口
Editor.md 自带图片上传按钮,但默认是弹 URL 输入框。要接自己的上传接口,需要配置imageUpload和imageFormats。
editormd("editor", { path: "/static/editormd/lib/", imageUpload: true, imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp"], imageUploadURL: "/upload", onload: function() { // 编辑器加载完成后的初始化 } });逻辑说明:imageUploadURL指向 Flask 的上传路由,Editor.md 会把文件以multipart/form-data发过去,字段名通常是editormd-image-file。后端必须返回 JSON,格式为{"success": 1, "url": "图片地址"},否则编辑器会提示上传失败。
4.2 Flask 端转发到 sm.ms
import requests from flask import request, jsonify from flask_login import login_required @login_required def upload(): file = request.files.get('editormd-image-file') if not file: return jsonify({"success": 0, "message": "没有文件"}) # 读取 token,没有则匿名上传 token = current_app.config.get('SM_MS_TOKEN', '') headers = {} if token: headers['Authorization'] = token files = {'smfile': (file.filename, file.stream, file.mimetype)} resp = requests.post( 'https://sm.ms/api/v2/upload', files=files, headers=headers, timeout=15 ) data = resp.json() if data.get('success'): return jsonify({"success": 1, "url": data['data']['url']}) # 重复上传时 sm.ms 返回 images 字段 if data.get('code') == 'image_repeated': return jsonify({"success": 1, "url": data['images']}) return jsonify({"success": 0, "message": data.get('message', '上传失败')})参数说明:timeout=15防止请求挂死;Authorization头在有 token 时带上,能提高配额;image_repeated是 sm.ms 对同一张图重复上传的返回,直接复用已有 URL,避免报错。注意file.stream在 Flask 里可直接传给 requests,不要先file.read()再传,大图会占内存。
4.3 上传失败的排查顺序
先看浏览器 Network 里/upload的响应,如果是 302 跳登录,说明@login_required生效但会话丢了;如果是 500,看 Flask 控制台报错;如果返回success: 0,看message字段。sm.ms 常见错误是 token 无效或频率超限,换 token 或等几分钟再试。
5. 避坑与常见问题:登录、自动保存、图床的翻车记录
5.1 登录后立刻跳回登录页
现象:输入账号密码后页面刷新,又回到/login。原因通常是SECRET_KEY没设置或每次重启变化,导致 Flask-Login 的 session 无法解码。解决:在self.cfg里写死一个随机字符串,确保每次启动一致。
5.2 自动保存把空内容覆盖了原文
现象:打开编辑页还没动,几秒后文章变空。原因是onchange在编辑器初始化时被触发,unsaved被置为true,而getValue()此时返回空。解决:在onload回调里先把unsaved设为false,或者加一个「内容长度大于 0 才提交」的判断。
5.3 图片上传成功但编辑器不显示
现象:Network 里/upload返回了 URL,但编辑区没插入图片。原因是返回 JSON 的字段名不对,Editor.md 要求success为 1 且url存在。如果后端返回的是{"code": 0, "data": {...}},编辑器不认。解决:严格按{"success": 1, "url": "..."}返回。
5.4 SQLite 数据库锁死
现象:多人同时保存时偶尔报database is locked。SQLite 默认写锁是串行的,并发高就排队。解决:本地单人用没问题,如果要多人用,换 PostgreSQL 或 MySQL,改SQLALCHEMY_DATABASE_URI即可,ORM 层不用动。
5.5 Editor.md 工具栏图标不显示
现象:编辑区能写,但工具栏是一排空白按钮。原因是editormd.min.css没引入,或者path配错导致字体文件 404。解决:检查 HTML 里 CSS 引入顺序,确认path指向的lib目录下有fonts文件夹。
6. 进阶:把自动保存做成可恢复的草稿机制
自动保存只解决「不丢」,不解决「改错了想回退」。我一般会在保存接口里加一个草稿表,每次保存前把旧内容写进draft表,保留最近 10 个版本。这样即使自动保存覆盖了,也能从数据库里捞回上一版。
# 保存时先写草稿,再更新正文 def save_article(article_id, new_content): article = Article.query.get(article_id) # 旧内容存入草稿表 draft = Draft(article_id=article_id, content=article.content) db.session.add(draft) article.content = new_content db.session.commit() # 只保留最近 10 条草稿 drafts = Draft.query.filter_by(article_id=article_id)\ .order_by(Draft.id.desc()).offset(10).all() for d in drafts: db.session.delete(d) db.session.commit()参数说明:offset(10)表示跳过最新 10 条,删掉更早的,控制表体积。这个逻辑放在保存接口里,对前端透明。验证方法是手动改几次内容,然后查draft表看是否有历史记录。
另一个进阶点是 Markdown 换行和表格的预览一致性。Editor.md 默认遵循 CommonMark,单个换行不产生<br>,如果你习惯 Typora 的换行行为,需要在初始化时配breaks: true。表格转换 Excel 这类需求不在编辑器内做,导出后用脚本处理更稳。
从那以后我每次接在线编辑器,都强制先跑一遍「登录 → 编辑 → 自动保存 → 刷新恢复 → 传图」这条链路,确认没有玄学丢内容才继续改功能。希望帮到你。
本文还有配套的精品资源,点击获取