当前位置: 首页
编程语言
不花钱搭建Flask知识库完整指南

不花钱搭建Flask知识库完整指南

热心网友 时间:2026-06-11
转载

为应对运维同事离职留下的零散交接材料,开发了一个基于Flask的轻量级知识库。后端采用Flask+SQLAlchemy+SQLite,前端使用原生JavaScript,零构建步骤。核心功能包括AES-256-GCM与RSA-2048混合加密保护文档内容、树形节点细粒度权限分享、Markdown编辑器分屏拖拽与双向滚动同步,以及Word Excel文件自动转M

1、起因:为什么要自己造轮子

要不是运维同事要离职还留下一堆零散的交接材料,真不太想自己动手再造一个轮子。过去同事间做交接,基本都是SVN归档配文档指引,这套流程本身没毛病,就是真用起来让人抓狂。比如你想查个东西,得先回想这玩意儿存哪儿了,然后再去具体位置翻内容……说实在的,费时费力。

不想花钱写了一个 Flask 知识库

更何况,交接完不可能立刻把所有细节烂熟于心,等出了紧急状况才去翻档案,那场面完全就是“抓马”本马。这时候,要是能有一个在线统一归档、随查随用的知识库该多好。

说实话,第一反应是——这有什么难的,Confluence不就在那里(之前还分享过怎么搭Jira和Confluence)。但仔细琢磨了一下,问题来了:

  1. Confluence 太重了。Ja va一跑起来,内存先吃掉2G,服务器资源不够根本扛不住。而且功能也太复杂了,大部分功能根本用不上,纯属资源浪费。
  2. 部署贼麻烦。不管用MediaWiki还是Wiki.js,光是折腾数据库、Node版本、反向袋里配置就能消耗半天精力。
  3. 数据安全问题。大多数Wiki系统,数据库里的内容都是明文存的。运维文档里难免有敏感信息——服务器IP、密码规则、内部拓扑。数据库文件被人拖走了怎么办?这种事情不是没发生过。就更别提用Notion、语雀、印象笔记这些第三方知识库了。
  4. 权限控制太死板。团队需要的是“这篇文章只给张三和李四看”,而不是简单的“管理员/普通用户”二元权限。

挑来挑去,实在找不到同时满足轻量部署 + 内容加密 + 细粒度分享这三个条件的工具。所以这个周末,干脆动手自己写了一个。

2、技术选型:为什么是 Flask + 原生 JS

不少人看到这个选型会觉得奇怪:“都2026年了,谁还用Flask写Web应用?前端连个框架都不上?”

2.1、后端:Flask + SQLAlchemy + SQLite

选Flask的理由其实很简单:够用,部署成本约等于零

整个应用就一个 app.py 入口,pip install 装完依赖就能直接跑。不需要Nginx、不需要WSGI配置、不需要数据库迁移脚本——db.create_all() 一行代码搞定一切。

SQLite确实有并发写的争议。但内部知识库的写入频率低得可怜(一天可能就改几篇文档),WAL模式足够应付。真到了并发瓶颈的时候,把连接字符串换成PostgreSQL就行,这也正是使用SQLAlchemy的理由。

2.2、前端:Vanilla JS,零构建步骤

必须承认,前端水平比较渣(所以这部分适当借助AI做了些东西)。

前端技术大概停留在10年前的水平,为了自己能看懂,不做SPA路由、不做虚拟DOM、不上Webpack。所有UI逻辑散落在几个JS文件里,页面只有一个 index.html(操作DOM就操作DOM,能用就好)。而且零构建意味着:改一行JS直接刷新浏览器就能看到效果。没有 npm run dev、没有HMR抽风、没有 node_modules 黑洞。说实在的,这体验相当好。

3、加密方案:拿了你也看不到

这是整个项目里最花心思的部分。

3.1、设计目标

简单说就一条:即使有人物理拿到了 wiki.sqlite 文件,他也读不懂任何一篇文章的内容

不少团队觉得“反正是内网部署,加密没必要”。但现实是——服务器被攻破、备份文件泄露、离职员工带走数据,这些事发生的概率一点都不低,等出了问题根本来不及处理。

3.2、技术实现:AES-256-GCM + RSA-2048 混合加密

核心思路和Telegram、WhatsApp的端到端加密类似,做了简化:

1. 用户注册 → 服务端自动生成 RSA-2048 密钥对
2. 创建 article → 随机生成 AES-256 密钥 → AES-GCM 加密内容 → RSA 公钥加密 AES 密钥
3. 存储到数据库 → encrypted_content(密文) + encrypted_key(加密后的 AES 密钥)
4. 读取 article → RSA 私钥解密 AES 密钥 → AES 密钥解密内容 → 返回明文

代码其实不复杂,核心就80行:

def encrypt_content(plaintext: str, public_key_pem: str) -> tuple[str, str]:
    aes_key = AESGCM.generate_key(bit_length=256)
    aesgcm = AESGCM(aes_key)
    nonce = os.urandom(12)
    ciphertext = aesgcm.encrypt(nonce, plaintext.encode('utf-8'), None)
    encrypted_content = base64.b64encode(nonce + ciphertext).decode('utf-8')    pubkey = serialization.load_pem_public_key(public_key_pem.encode('utf-8'))
    encrypted_key = pubkey.encrypt(
        aes_key,
        asym_padding.OAEP(
            mgf=asym_padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None,
        ),
    )
    return encrypted_content, base64.b64encode(encrypted_key).decode('utf-8')

4个设计方向:

  1. 密钥存在服务端。这个方案并不是真正的端到端加密,密钥对存储在数据库的 User 表里。肯定会有人问“那服务端被完全攻破了怎么办?”答案是:如果攻击者拿到了运行时的内存和数据库,确实很难防。但防御目标是“数据库文件被拖走”这个场景——攻击者只拿到静态文件,没有解密密钥。
  2. document 节点不加密,article 节点加密。目录结构没必要加密,不然连树都渲染不了。只有文章内容走加密通道就可以了。
  3. 分享时的重新加密。分享文档给另一个用户时,系统会用对方的RSA公钥重新加密AES密钥。对方用自己的私钥就能解密。权限回收同理,删除对应的 NodePermission 记录就行,内容本身不需要动。
  4. 权限转让更复杂。如果把文档所有权转给另一个人,所有子节点的AES密钥都要用新所有者的公钥重新加密。递归转让时每个节点独立处理,若有100个节点的目录,就会有100次RSA加密操作。不过好在这是低频操作,性能不是问题。

3.3、一个真实的防御场景

假设服务器在联通云上,有人通过某个漏洞拿到了文件系统的访问权限,把 wiki.sqlite 下载走了。他能看到什么?

  • User 表的邮箱、注册时间(能猜到是谁)
  • Node 表的标题和树形结构(能看到文档目录)
  • Node 表的 content 字段(AES-GCM 密文,没有密钥解不开)
  • Node 表的 encrypted_key 字段(RSA-2048 加密的 AES 密钥,没有私钥解不开)

说实话,标题泄露也是信息泄露。但和全文泄露相比,这个风险对于一般小微企业来说还是可以接受的。如果未来需要更彻底的方案,可以引入客户端加密——密钥存在浏览器 localStorage 里,服务端完全不碰明文。

4、编辑器体验:三个满意的细节

编辑器是用户接触最多的部分,这里参考了别人家(Markdown编辑器)的经验:

4.1、分屏拖拽吸附

左右分屏(Markdown源码 / HTML预览),中间有一条可拖拽的分隔条。但常规拖拽有个问题——很难精确拖到“全屏Markdown”或“全屏预览”。做法是加了一个吸附逻辑:

const SNAP_THRESHOLD = 20;  // 距离边缘 20px 以内触发吸附
// 拖到最左边  → HTML 预览全屏(markdown pane 折叠)
// 拖到最右边  → Markdown 全屏(html pane 折叠)
// 中间任意位置 → 自由比例分屏

这个交互比想象中好用。写长文档时全屏Markdown,校对排版时全屏预览,平时分屏对照——三种模式切换零成本。

4.2、双向滚动同步

实现方案是比例映射:两边各自计算 scrollTop / (scrollHeight - clientHeight) 的比例,然后按这个比例设置另一侧的滚动位置。用 requestAnimationFrame 防抖,再用一个全局锁 isScrollSyncing 防止两边互相触发形成死循环。

按比例映射在文档结构差异比较大时(比如Markdown开头50行大部分是YAML front matter,在HTML里只渲染成一行),同步精度会下降。但对于普通的技术文档来说,够用了。

4.3、Ctrl+S 保存

这个功能很小,但没做的话体验会很差。如果用户写了一篇文章忘记保存就关了页面,那种感觉……懂的都懂。

beforeunload 事件 + isDirty 标记追踪未保存状态:

window.addEventListener('beforeunload', (e) => {
    if (WikiEditor.isUnsa ved()) {
        e.preventDefault();
        e.returnValue = '';
    }
});

5、文件导入:Word 和 Excel 直接转 Markdown

运维有很多存量文档是Word格式的——故障排查、操作手册、部署文档等等。让人一篇一篇重新用Markdown写完全不现实。

所以做了三个导入通道:

  1. Markdown / 纯文本 / 代码文件:直接读内容,不转换
  2. Word (.docx):用 python-docx 解析,标题样式(Heading 1~4)自动映射到Markdown标题层级,正文转普通段落。图片提取保存到 images/ 目录,并保留在文档中原段落的位置
  3. Excel (.xlsx):用 openpyxl 解析,每个 Sheet 渲染为独立的Markdown表格,以 ## Sheet名称 作为二级标题

导入逻辑的大致流程:

  1. 先扫描 doc.part.rels,把文档里所有图片的关系ID(rId)和实际图片数据对应起来,保存到 images/ 目录
  2. 然后逐段落遍历,用 qn("w:drawing") 检测当前段落是否包含图片
  3. 没图片的段落:直接按标题样式或正文提取文本
  4. 有图片的段落:按 元素的原始顺序逐个处理,在文本和图片之间保持相对位置

python-docx 的API在图片提取这块支持不太好,需要手动遍历OOXML的 元素才能拿到 r:embed 属性去查关系ID。

6、权限与分享:不只是简单的 RBAC

大多数Wiki系统用的是“空间级”权限——要么能看整个空间,要么什么都看不了。但实际需求往往是这样:

树形节点 + 独立的授权记录表,天然支持这种粒度:

-- 权限模型
User (id, email, rsa_public_key, rsa_private_key, ...)
Node (id, parent_id, title, content, owner_id, encrypted_key, ...)
NodePermission (node_id, grantor_id, grantee_id, is_active)

关键设计:

  • 授权不是给用户分配角色,而是给节点绑定被授权人。每个 NodePermission 记录代表“用户A授权用户B看节点C”
  • 被分享者看到只读视图:全宽HTML预览,隐藏Markdown编辑器和保存按钮
  • 祖先节点可见:被分享的节点在分享对象的树中,能看到完整的父级链——虽然父节点没有直接权限,但节点路径需要完整显示才有意义

批量操作也做了:Ctrl+Click多选节点,然后批量授权或撤销。

还有个有意思的场景——权限转让。比如原来的文档负责人离职了,需要把所有权转给别人:

  1. 选中节点 → 右键 → 权限转让 → 选择新所有者
  2. 系统用旧所有者的私钥解密所有子节点的AES密钥
  3. 用新所有者的公钥重新加密
  4. 更新 owner_id

每个节点独立重新加密。处理时间大概几秒钟,对体验影响不大。

7、一些不完美的地方(坦白局)

7.1、前端没有做路由

所有页面状态靠JS变量维护,刷新页面就回到初始状态。虽然SPA本来就是这样,但没有URL状态意味着你没法复制一个链接发给同事说“你看这篇文档”。这是未来需要加的功能——至少把 ?node_id=123 映射到对应的文档打开状态。

7.2、密钥存在服务端

前面说过了。这是安全性和便利性的权衡。如果引入客户端加密,分享功能会复杂很多——需要客户端交换公钥。目前这个方案对内部团队来说够用,但如果要开放公网注册,这个设计需要重新评估。

7.3、SQLite 的并发天花板

虽然内部Wiki不太可能触发SQLite的并发瓶颈,但如果一个团队有几百人同时编辑,确实会出现写锁冲突。好在换成PostgreSQL只需要改一行配置:

# config.py
SQLALCHEMY_DATABASE_URI = 'postgresql://user:pass@localhost/wiki'

ORM层完全不用动。

7.4、没有做 i18n

目前所有的UI文本、错误提示都是硬编码的中文。如果有国际化需求,需要重构一遍。不过对国内运维团队来说这不是什么问题。

8、部署:比冲一杯咖啡还快

故意把部署成本压到了最低:

pip install -r requirements.txt
python app.py

就这两步。默认监听 0.0.0.0:5100,浏览器打开就能用。

第一个注册的用户自动成为管理员。之后任何人注册都需要管理员在后台审批——避免匿名用户往Wiki里灌垃圾内容。

生产环境挂gunicorn:

gunicorn -w 4 -b 0.0.0.0:5100 app:create_app()

邮件通知是可选的,配了SMTP就用,不配也不影响核心功能。

9、最后:适合谁用

Ops Wiki 不是要替代Confluence或Notion。它解决的是一个很具体的场景:

如果符合这个场景,拿去用吧。如果不符,它至少展示了“不用框架、不用构建工具、用原生JS + Flask做一个完整的Web应用是什么体验”。

来源:https://juejin.cn/post/7648424256877821967

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
IDEA中SpringBoot项目热部署实现指南

IDEA中SpringBoot项目热部署实现指南

Springboot项目在IDEA中实现热部署需依次完成:开启自动编译(静态与动态编译)、启用热部署策略、引入spring-boot-devtools依赖、关闭浏览器缓存,最后启动测试即可生效,省去手动重启时间,大幅提升开发效率。

时间:2026-07-11 06:47
Java实现Excel转JSON的代码详解

Java实现Excel转JSON的代码详解

使用Java结合FreeSpire XLS库可自动将Excel转为JSON,通过读取工作表并映射为键值对,高效支持数据迁移、报表导出与系统对接,大幅提升效率并消除手动录入错误。

时间:2026-07-11 06:47
Golang高效布谷鸟过滤器多字符集字符串过滤

Golang高效布谷鸟过滤器多字符集字符串过滤

布谷鸟过滤器支持删除操作,通过指纹截断与双哈希定位实现多字符集高效过滤。指纹截断需采用fnv64a或xxhash快速哈希并取低8位,桶索引使用独立双哈希避免假阳性。并发安全需以固定数组配合sync atomic实现。

时间:2026-07-11 06:47
Java使用Poi-tl按Word模板生成动态表格

Java使用Poi-tl按Word模板生成动态表格

Poi-tl是Word导出工具,文档周全,支持多级合并表头生成。通过{{text}}、{{?var}}、{{ table}}标签处理模板,传入TableRenderData对象即可动态渲染表格。示例展示用户信息表格生成,并提供工具类消除表格首行缩进,确保格式正确。

时间:2026-07-11 06:47
Go语言微服务集成Swagger生成交互式API文档完整教程

Go语言微服务集成Swagger生成交互式API文档完整教程

在Go微服务中集成Swagger常见四大问题:swaginit初始化失败需确保存在packagemain及注释;SwaggerUI加载失败因路由映射错误或未导入docs;@Param注解必须严格遵循格式;部署后接口文档显示localhost因硬编码host,应删除或通过环境变量动态注入。

时间:2026-07-11 06:47
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 热门数据榜