欢迎光临
我们一直在努力

【没事学点啥】TurboBlog个人化轻量博客项目——Turbo Blog 项目学习与上线指南

Turbo Blog 项目学习与上线指南

整理日期:2026-05-06
项目定位:一个轻量个人博客。Django 像发动机,负责数据、接口、登录、评论、上传、RSS 和 sitemap;原生 HTML/CSS/JS 像驾驶舱,负责页面、写作后台和交互。

效果如图所示:
请添加图片描述

1. 项目总览表

模块作用关键文件你需要记住的一句话
前端入口 浏览器打开的第一张纸 index.html 只挂载 #app,再加载 config.js 和 script.js。
前端配置 决定 API 请求打到哪里 config.js 同域部署时留空;前后端分开时填后端公网地址。
前端逻辑 页面渲染、路由、写作、评论、AI 问答 script.js 所有前端“大脑”都在这里。
前端样式 颜色、布局、响应式、卡片、表单 styles.css 页面长相基本都改这里。
站点图片 头像、上传图、分享图 assets/ 固定头像在根图片,后台上传图在 assets/uploads/。
Django 配置 环境变量、数据库、上传目录、AI 配置 turboblog/settings.py 项目的总电闸。
Django 路由 URL 到函数的分发 turboblog/urls.py 看到接口地址,先来这里找对应函数。
数据模型 数据库表结构 blog/models.py 文章、评论、用户、会话、验证码都在这里定义。
业务接口 API、RSS、sitemap、静态页面服务 blog/views.py 后端主战场,绝大多数行为都在这里。
跨域处理 CORS 和 OPTIONS blog/middleware.py 允许前端跨域调用后端。
初始化数据 从旧 JSON 导入站点与文章 blog/management/commands/seed_initial_data.py 空库第一次启动时喂一点内容。
默认数据库 SQLite 数据文件 backend/data/turbo-blog-django.sqlite 本地真实数据在这里。
旧数据种子 初始文章、站点信息 backend/data/db.json 只影响首次导入或重建库。
启动脚本 Windows 本地启动/停止 start-blog.bat、stop-blog.bat 双击即可本地跑起来。
部署配置 云平台启动命令 Procfile Heroku/Railway 类平台会读它。
测试脚本 后端冒烟检查 scripts/test_django_backend.py 改完后跑它,像出门前检查钥匙。

当前本地数据库概况:站点名 Turbo Blog,公网地址仍是 http://localhost:5173,共有 4 篇文章,4 篇已发布文章,1 条评论,0 条待审评论。

2. 架构图

#mermaid-svg-P5hGDFqfISYibOJY{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-P5hGDFqfISYibOJY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-P5hGDFqfISYibOJY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-P5hGDFqfISYibOJY .error-icon{fill:#552222;}#mermaid-svg-P5hGDFqfISYibOJY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-P5hGDFqfISYibOJY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-P5hGDFqfISYibOJY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-P5hGDFqfISYibOJY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-P5hGDFqfISYibOJY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-P5hGDFqfISYibOJY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-P5hGDFqfISYibOJY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-P5hGDFqfISYibOJY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-P5hGDFqfISYibOJY .marker.cross{stroke:#333333;}#mermaid-svg-P5hGDFqfISYibOJY svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-P5hGDFqfISYibOJY p{margin:0;}#mermaid-svg-P5hGDFqfISYibOJY .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-P5hGDFqfISYibOJY .cluster-label text{fill:#333;}#mermaid-svg-P5hGDFqfISYibOJY .cluster-label span{color:#333;}#mermaid-svg-P5hGDFqfISYibOJY .cluster-label span p{background-color:transparent;}#mermaid-svg-P5hGDFqfISYibOJY .label text,#mermaid-svg-P5hGDFqfISYibOJY span{fill:#333;color:#333;}#mermaid-svg-P5hGDFqfISYibOJY .node rect,#mermaid-svg-P5hGDFqfISYibOJY .node circle,#mermaid-svg-P5hGDFqfISYibOJY .node ellipse,#mermaid-svg-P5hGDFqfISYibOJY .node polygon,#mermaid-svg-P5hGDFqfISYibOJY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-P5hGDFqfISYibOJY .rough-node .label text,#mermaid-svg-P5hGDFqfISYibOJY .node .label text,#mermaid-svg-P5hGDFqfISYibOJY .image-shape .label,#mermaid-svg-P5hGDFqfISYibOJY .icon-shape .label{text-anchor:middle;}#mermaid-svg-P5hGDFqfISYibOJY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-P5hGDFqfISYibOJY .rough-node .label,#mermaid-svg-P5hGDFqfISYibOJY .node .label,#mermaid-svg-P5hGDFqfISYibOJY .image-shape .label,#mermaid-svg-P5hGDFqfISYibOJY .icon-shape .label{text-align:center;}#mermaid-svg-P5hGDFqfISYibOJY .node.clickable{cursor:pointer;}#mermaid-svg-P5hGDFqfISYibOJY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-P5hGDFqfISYibOJY .arrowheadPath{fill:#333333;}#mermaid-svg-P5hGDFqfISYibOJY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-P5hGDFqfISYibOJY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-P5hGDFqfISYibOJY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P5hGDFqfISYibOJY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-P5hGDFqfISYibOJY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P5hGDFqfISYibOJY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-P5hGDFqfISYibOJY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-P5hGDFqfISYibOJY .cluster text{fill:#333;}#mermaid-svg-P5hGDFqfISYibOJY .cluster span{color:#333;}#mermaid-svg-P5hGDFqfISYibOJY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-P5hGDFqfISYibOJY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-P5hGDFqfISYibOJY rect.text{fill:none;stroke-width:0;}#mermaid-svg-P5hGDFqfISYibOJY .icon-shape,#mermaid-svg-P5hGDFqfISYibOJY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P5hGDFqfISYibOJY .icon-shape p,#mermaid-svg-P5hGDFqfISYibOJY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-P5hGDFqfISYibOJY .icon-shape .label rect,#mermaid-svg-P5hGDFqfISYibOJY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P5hGDFqfISYibOJY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-P5hGDFqfISYibOJY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-P5hGDFqfISYibOJY :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

浏览器

index.html

config.js

script.js

/api/… 请求

turboblog/urls.py

blog/views.py

blog/models.py

backend/data/turbo-blog-django.sqlite

assets/uploads/

RSS / sitemap / robots

可选 AI 模型 API

这不是 React/Vue 项目,而是“原生前端 + Django 单体后端”。浏览器加载静态文件,script.js 用 fetch() 调 Django API;Django 再读写 SQLite,必要时保存上传图片。

3. 要改内容时去哪里

你想改什么去哪个文件具体位置/关键词
浏览器标题、SEO 描述、分享图 index.html <title>、meta name="description"、og:*
API 后端地址 config.js window.TURBO_BLOG_CONFIG.apiBase
首页大标题和介绍文案 script.js hero()
顶部导航、底部版权 script.js layout()
AI 小助手区域文案 script.js aiAssistantPanel()
首页文章列表、筛选、短札、关于 script.js renderHome()
文章详情页 script.js renderPost()
写作后台表单 script.js renderEditor()
管理员登录弹窗 script.js adminLoginModal()
前端按钮点击、提交表单、上传图片 script.js bindEvents()
前端 Markdown 渲染规则 script.js markdownToHtml()、renderInlineMarkdown()
颜色、字体、间距、移动端布局 styles.css :root、对应 class、@media
头像/站点图标 assets/avatar-blue-anime.png 替换同名图片最省事
上传图片存储位置 assets/uploads/ 后台上传后自动写入
API 路由增删 turboblog/urls.py urlpatterns
文章字段、评论字段、用户字段 blog/models.py Post、Comment、BlogUser 等模型
文章增删改查逻辑 blog/views.py posts()、post_detail()
评论提交和审核逻辑 blog/views.py post_comments()、admin_comments()、admin_comment_detail()
验证码、限流、敏感词 blog/views.py、turboblog/settings.py captcha()、is_rate_limited()、SENSITIVE_WORDS
图片上传大小和格式 blog/views.py、turboblog/settings.py upload_image()、IMAGE_UPLOAD_LIMIT
AI 检索和 RAG 逻辑 blog/views.py、turboblog/settings.py ai_ask()、best_post_passages()、call_rag_model()
SQLite 文件位置 turboblog/settings.py SQLITE_FILE
生产环境密钥、域名、Debug turboblog/settings.py 环境变量读取区
初始文章内容 backend/data/db.json 只在空库导入时生效
初始化导入规则 blog/management/commands/seed_initial_data.py handle()
本地启动逻辑 start-blog.bat 端口、迁移、启动命令
停止本地服务 stop-blog.bat 端口 4000,5173,8000
云平台启动命令 Procfile web: 后面的命令

manage.py 的作用:它是 Django 的“控制开关”。你运行这些命令时都靠它找到项目配置:

python manage.py runserver python manage.py migrate python manage.py
check python manage.py seed_initial_data

它里面最关键的一行是:

os.environ.setdefault(“DJANGO_SETTINGS_MODULE”, “turboblog.settings”)

意思是:告诉 Django,配置文件在 turboblog/settings.py。
注意:站点标题、描述、公网地址已经存进 SQLite 后,改 backend/data/db.json 不会自动覆盖旧库。已有库要改 public_url,可用:

python manage.py shell -c "from blog.models import SiteSetting; s=SiteSetting.objects.get(pk=1); s.public_url='https://你的域名'; s.save()"

4. 数据模型速记

模型数据库里的角色关键字段
SiteSetting 站点设置 title、description、public_url
BlogUser 管理员/作者账号 username、password_hash、role
Session 登录会话 token_hash、expires_at、revoked_at
Post 博客文章 title、slug、excerpt、content、tags、status
Comment 评论 post、author、content、approved、status
Captcha 评论验证码 answer_hash、expires_at、used、failures
RateLimit 评论频率限制 key、count、expires_at

最重要的关系:一篇 Post 可以有多条 Comment;评论默认 pending,管理员通过后才公开展示。

5. API 地图

接口方法用途是否需要管理员
/api/health GET 健康检查
/api/storage GET 查看存储状态
/api/site GET 获取站点信息
/api/posts GET 文章列表、搜索 否;带草稿需登录
/api/posts POST 新建文章
/api/posts/<slug或id> GET 文章详情 否;草稿需登录
/api/posts/<slug或id> PUT/DELETE 修改/删除文章
/api/posts/<slug或id>/comments POST 提交评论
/api/captcha GET 获取评论验证码
/api/admin/comments GET 待审/已审评论列表
/api/admin/comments/<id> PUT/DELETE 审核或删除评论
/api/uploads POST 上传图片
/api/auth/login POST 管理员登录
/api/auth/me GET 检查登录状态
/api/auth/logout POST 退出登录 是/有 token 更完整
/api/ai/ask POST 博客知识问答
/sitemap.xml GET 搜索引擎站点地图
/rss.xml GET RSS 订阅
/robots.txt GET 爬虫规则

6. 核心流程

6.1 打开首页

浏览器访问 /,Django 的 frontend() 返回 index.html;script.js 先 loadSite(),再 navigate(),最后 loadPosts() 拉文章并 renderHome() 画出首页。

6.2 写文章

点击“写作”后,前端先调用 /api/auth/me 检查管理员。登录通过后进入 renderEditor();保存时提交到 /api/posts 或 /api/posts/<id>,Django 写入 Post 表。

6.3 评论

文章详情页先拉 /api/captcha。访客提交评论时,后端检查验证码、敏感词和频率限制;评论进入 pending,管理员在写作后台通过后才展示。

6.4 图片上传

写作后台选择图片,前端转成 base64 发到 /api/uploads;后端校验格式和大小,然后写入 assets/uploads/,返回 Markdown 图片地址。

6.5 AI 问答

问题进入 /api/ai/ask。后端把问题分词,在已发布文章里找相关片段;没有 AI_API_KEY 时用本地检索回答,有 key 时把片段交给 DeepSeek/OpenAI-compatible 模型生成回答。

7. 本地开发命令

Windows 推荐:

start-blog.bat

手动启动:

python -m pip install -r requirements.txt
python manage.py migrate –noinput
python manage.py seed_initial_data
python manage.py runserver [::]:5173

检查:

python manage.py check
python scripts/test_django_backend.py

本地地址:

http://localhost:5173
http://localhost:5173/api/health
http://localhost:5173/sitemap.xml

本地默认管理员令牌/密码:dev-token。上线必须改。

8. 生产环境变量

变量生产建议作用
DJANGO_SECRET_KEY 必填,长随机字符串 Django 加密密钥
DJANGO_DEBUG 0 关闭调试模式
ALLOWED_HOSTS 你的域名,www.你的域名 允许访问的域名
ADMIN_TOKEN 强随机令牌 后台令牌登录
ADMIN_PASSWORD 强密码 管理员用户名密码登录
SQLITE_FILE 持久磁盘路径 SQLite 数据库位置
JSON_BODY_LIMIT 默认即可 JSON 请求体大小
IMAGE_UPLOAD_LIMIT 默认 5MB 上传图片大小
SENSITIVE_WORDS 按需加词 评论敏感词
AI_PROVIDER local/deepseek/openai AI 提供方
AI_API_KEY 有模型才填 模型 API Key
AI_MODEL 如 deepseek-chat 模型名称

9. Turbo Blog Django 公网发布指南

当前项目已经切换为 Django 后端:一个 Python 服务同时提供前端页面、博客 API、图片上传、RSS、sitemap 和 robots.txt。

整体发布流程如下:

  • 把项目推送到 GitHub。
  • 连接 GitHub 仓库。
  • 在 Railway里新建 Web Project和Volume。
  • 部署平台 + 持久磁盘 Volume + SQLite

    部署成功如图所示:
    在这里插入图片描述

    9.1Railway部署

    Railway 是什么
    Railway 是一个“云上自动机房”。你不用买服务器、装 Nginx、配 HTTPS,它会从 GitHub 拉代码,自动构建,运行你的 Django,并给你一个公网网址。

    你的链路会变成:

    GitHub 仓库
    -> Railway 自动拉代码
    -> 安装 requirements.txt
    -> 执行启动命令
    -> Gunicorn 跑 Django
    -> Railway 给公网域名和 HTTPS

    Volume 是什么
    Volume 不是“音量”,是“持久硬盘”。

    普通 Railway 容器像临时酒店房间:你可以在里面写文件,但重新部署/重启后,房间可能被打扫干净。
    Volume 像你租的储物柜:挂到服务里以后,文件会长期保留。

    你的博客长期写文章,最重要的是数据库不能丢,所以要让 SQLite 住进 Volume:

    /app/data/turbo-blog-django.sqlite

    Railway 官方说明:Volumes 用来给服务保存持久数据,挂载路径会变成服务里可读写的目录;Volume 在服务启动时挂载,不在 build/pre-deploy 阶段挂载。参考:

    python manage.py migrate –noinput && python manage.py seed_initial_data && gunicorn turboblog.wsgi:application –bind 0.0.0.0:$PORT

    项目也带了 Procfile,支持会读取 Procfile 的平台自动部署。

    9.2新建项目

    New Project
    -> Deploy from GitHub repo
    -> 选择你的博客仓库
    Railway 会生成一个项目,里面会有一个服务。这个服务就是你的 Django 博客容器

    9.3设置启动命令

    进入你的服务:

    Service
    -> Settings
    -> Deploy
    -> Start Command
    在这里插入图片描述

    python manage.py migrate –noinput && python manage.py seed_initial_data && gunicorn turboblog.wsgi:application –bind 0.0.0.0:$PORT

    9.4必填环境变量

    操作如下图所示:
    在这里插入图片描述
    之后直接复制下方代码:

    DJANGO_DEBUG=0
    DJANGO_SECRET_KEY=一串很长的随机字符
    ADMIN_TOKEN=你的后台强令牌
    ADMIN_PASSWORD=你的后台强密码
    ALLOWED_HOSTS=*
    SQLITE_FILE=/app/data/turbo-blog-django.sqlite
    UPLOAD_DIR=/app/data/uploads

    密钥可以本地生成:

    python -c "import secrets; print(secrets.token_urlsafe(50))"

    9.5Volume 创建

    先ctrl+k
    然后在这个搜索框里输入 volume
    接下来一直点
    最后Mount Path: /app/data
    结束

    9.6访问

    1、开全局才能访问的到网站
    2、如图所示,这才是你个人的网站
    在这里插入图片描述

    10. 上线前检查清单

    检查项目标
    DJANGO_DEBUG=0 不暴露调试信息
    DJANGO_SECRET_KEY 已更换 不用本地默认密钥
    ADMIN_TOKEN/ADMIN_PASSWORD 已更换 不用 dev-token
    ALLOWED_HOSTS 是真实域名 不长期使用 *
    SQLITE_FILE 在持久磁盘 重启/重建不丢文章
    assets/uploads/ 可持久保存 上传图片不丢
    已备份 SQLite 至少定期备份数据库文件
    SiteSetting.public_url 是 HTTPS 域名 sitemap/RSS 链接正确
    /sitemap.xml 可访问 搜索引擎可发现文章
    /robots.txt 可访问 爬虫规则正常
    页脚有 ICP/公安备案号 国内长期运行需要
    评论审核开启 访客评论不直接公开
    python scripts/test_django_backend.py 通过 核心接口没坏

    11. 快速学习路线

    按这个顺序学,像从地图边缘走到城市中心:

  • 先跑起来:用 start-blog.bat 打开 http://localhost:5173,写一篇测试文章。
  • 看入口:读 index.html,知道页面如何加载 JS/CSS。
  • 看前端状态:读 script.js 顶部的 state,它就是页面记忆。
  • 看请求封装:读 apiBase()、request()、apiHeaders(),理解前端怎样带 token 调接口。
  • 看页面渲染:读 renderHome()、renderPost()、renderEditor(),它们把数据变 HTML。
  • 看事件系统:读 bindEvents(),所有点击、输入、提交都在这里被接住。
  • 看路由:读 navigate(),理解 /、/post/…、/editor/… 怎么切页面。
  • 看 Django 路由:读 turboblog/urls.py,把 API 地址和视图函数连起来。
  • 看业务接口:读 blog/views.py,重点看文章、评论、上传、AI 四组函数。
  • 看数据表:读 blog/models.py,再对照 API 返回的数据。
  • 看配置:读 turboblog/settings.py,把环境变量、SQLite、上传目录、AI 配置看懂。
  • 看测试:读 scripts/test_django_backend.py,它是项目“应该怎样工作”的简版说明书。
  • 练手建议:

    练习修改文件收获
    把首页标题改成自己的话 script.js 的 hero() 熟悉渲染函数
    改主题色 styles.css 的 :root 熟悉样式变量
    新增一个文章标签筛选 script.js 的 categoryOf() 和 renderHome() 熟悉前端状态和筛选
    给文章加字段 blog/models.py、迁移、blog/views.py、script.js 走完整前后端链路
    改 AI 提示词 blog/views.py 的 rag_messages() 理解 RAG 边界
    加一个新 API turboblog/urls.py + blog/views.py 理解 Django 路由
    赞(0)
    未经允许不得转载:171主机测评 » 【没事学点啥】TurboBlog个人化轻量博客项目——Turbo Blog 项目学习与上线指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址