main
孤竹居士的个人博客
部署在阿里云 Ubuntu 上的个人全栈博客系统:原生前端 SPA + FastAPI + SQLite。
技术架构
| 层 | 技术 |
|---|---|
| 服务器 | Ubuntu Linux + Nginx |
| 前端 | 原生 HTML + CSS + JavaScript(SPA,History API 路由) |
| 后端 | Python FastAPI(仅监听 127.0.0.1:8080) |
| 数据库 | SQLite + SQLAlchemy ORM |
| 认证 | JWT Token(bcrypt 存储密码哈希) |
| 文件 | Nginx 静态访问 uploads 目录 |
- Nginx:80(HTTP)/ 443(HTTPS,待配置);
/api/*反代到127.0.0.1:8080;/uploads/*静态映射。 - 端口:22 SSH、80 HTTP、443 HTTPS、25565 Minecraft(独立运行,不占用博客端口)。
目录结构
blog/
├── frontend/ # 前端源码(原生 SPA,ES Modules 拆分)
│ ├── index.html
│ ├── style.css
│ ├── main.js # 入口:初始化 + 全局事件
│ ├── api.js # 网络层:api() / uploadFile()
│ ├── auth.js # 登录 / 注册 / 会话
│ ├── router.js # 路由与页面壳(导航栏、用户区)
│ ├── article.js # 文章列表 / 详情 / 分区 / 简介视图
│ ├── comment.js # 评论与回复
│ ├── friend.js # 好友申请
│ ├── manage.js # 博主管理面板(发文 / 编辑 / 简介设置)
│ ├── markdown.js # Markdown 轻量渲染(含 URL 白名单)
│ ├── state.js # 全局常量与状态
│ └── utils.js # DOM / 日期 / 弹窗 / Toast 工具
├── backend/
│ ├── main.py # FastAPI 实例 + 路由注册 + 统一错误格式
│ ├── database.py # SQLite 连接与初始化
│ ├── models.py # ORM 模型(User/Article/Comment/Like/Friend/EmailCode)
│ ├── schemas.py # Pydantic 请求/响应模型
│ ├── auth.py # bcrypt + JWT + 当前用户依赖
│ ├── security.py # 邮箱校验、IP 提取、内存限流器
│ ├── email.py # SMTP 验证码发送
│ ├── seed.py # 博主账号初始化(python -m backend.seed)
│ └── routers/ # 业务路由(user/article/comment/like/friend/email/password/upload)
├── uploads/
│ ├── avatar/ # 头像
│ ├── article/ # 文章图片(封面/插图)
│ └── project/ # 项目文件(zip)
├── .env # 环境配置(已被 .gitignore 忽略,勿提交)
├── .env.example # 环境变量模板(全中文注释)
├── requirements.txt # Python 依赖
└── README.md
快速开始(本地开发)
- 安装依赖:
pip install -r requirements.txt - 配置环境:复制
.env.example为.env,填写JWT_SECRET、BLOGGER_*、SMTP_*。 - 初始化数据库与博主:
python -m backend.seed - 启动后端:
uvicorn backend.main:app --host 127.0.0.1 --port 8080 - 启动前端:任意静态服务器指向
frontend/,并将/api/*反代到127.0.0.1:8080、/uploads/*映射到uploads/(本地开发可借助 Nginx 或简单代理脚本实现)。
提示:前端为 SPA,任意路径(如
/article/1)都应回退到index.html。
权限与角色
| 角色 | 权限 |
|---|---|
| visitor(游客) | 查看公开文章、申请好友(好友文章仅显示标题与封面) |
| friend(好友) | 查看公开与好友文章、评论(含回复)、点赞 |
| blogger(博主) | 全部权限:发布/编辑/删除文章、审批好友申请、上传图片与项目、修改简介与头像 |
API 一览(统一前缀 /api,统一响应 {success, data, message})
认证与资料
POST /api/register注册(邮箱/用户名/密码,bcrypt 存储)POST /api/login登录(JWT;失败限流)GET /api/user/level当前角色GET /api/user/me当前用户资料GET /api/user/blogger博主公开资料(简介页)PUT /api/user/profile更新头像/简介
文章
POST /api/article/add发布(仅博主)GET /api/article/list?page=&page_size=列表(分页;好友文章对游客仅标题+封面)GET /api/article/{id}详情(好友文章对游客锁定正文)PUT /api/article/{id}编辑(仅博主)DELETE /api/article/{id}删除(仅博主,级联评论/点赞)
评论 / 点赞 / 好友
POST /api/comment/add评论或回复(parent_id可选;仅好友/博主)GET /api/comment/list?article_id=评论列表POST /api/like/add点赞(好友/博主,不可重复)GET /api/like/list?article_id=点赞列表POST /api/friend/apply申请好友 /GET /api/friend/status好友状态GET /api/friend/applications申请列表(仅博主)POST /api/friend/{id}/approve|reject审批(仅博主)
邮箱验证码 / 密码
POST /api/email/send-code发送验证码(邮箱+IP 限流)POST /api/email/verify-code校验验证码(尝试次数限制)POST /api/password/forgot忘记密码 /POST /api/password/reset重置密码
上传
POST /api/upload/avatar头像(jpg/png/webp,≤2MB)POST /api/upload/article文章图片(仅博主,≤5MB)POST /api/upload/project项目文件(仅博主,zip,≤50MB)
安全说明
- 密码 bcrypt 哈希存储,禁止明文;localStorage 仅保存 token 与用户名。
- JWT_SECRET 缺失时后端拒绝启动(fail-fast),禁止硬编码弱密钥。
- 登录、验证码发送/校验均有内存限流(阈值可在
.env调整)。 - 上传文件:白名单扩展名 + 魔数校验 + 随机文件名,禁止执行用户上传内容。
- 前端 Markdown 渲染带 URL 协议白名单(
javascript:/data:等被拦截)+ CSP 响应头/标签(纵深防御)。 - 后端仅监听 127.0.0.1:8080,禁止公网直连。
- 注册接口对“邮箱 / 用户名已占用”返回统一提示,且先核验验证码再查唯一性(防枚举);注册失败会作废本次验证码,需重新发送后再试。
部署(阿里云)
详细的 Nginx 站点配置与 systemd 服务文件将在后续任务中补充(当前仅给出要点)。
- 上传项目到
/var/www/blog,安装依赖:pip install -r requirements.txt。 - 配置
.env(强随机JWT_SECRET、博主账号、SMTP、安全限流)。 python -m backend.seed初始化数据库与博主。- Nginx:80 端口托管
frontend/静态文件;/api/*反代127.0.0.1:8080;/uploads/*映射uploads/;SPA 回退index.html。 - systemd 守护
uvicorn(开机自启、崩溃重启)。 - 安全组仅开放 22 / 80 / 443 / 25565。
数据库升级说明(仅旧库需要)
全新部署无需迁移:python -m backend.seed 会自动按最新结构建表。
若从旧版本升级(服务器上已有 backend/blog.db),需先补齐新字段再重启服务:
# 服务器(Ubuntu)执行;正式升级前建议先备份 backend/blog.db 与 uploads/
cd /var/www/blog
python3 - <<'EOF'
import sqlite3, datetime
con = sqlite3.connect("backend/blog.db")
con.execute("ALTER TABLE users ADD COLUMN token_version INTEGER NOT NULL DEFAULT 0")
con.execute("ALTER TABLE friends ADD COLUMN created_time DATETIME")
con.execute("ALTER TABLE articles ADD COLUMN category VARCHAR(30) NOT NULL DEFAULT 'life'")
con.execute("ALTER TABLE users ADD COLUMN avatar_updated_time DATETIME")
now = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) # 与后端 utcnow() 一致:无时区 UTC
con.execute("UPDATE friends SET created_time=? WHERE created_time IS NULL", (now,))
con.commit()
con.close()
print("数据库升级完成")
EOF
本地开发(Windows)若仅为调试数据,可省略迁移:删除 backend/blog.db 后重新执行 python -m backend.seed 即可。
字段说明:
users.token_version:JWT 令牌版本号。重置密码后版本自增,该用户所有旧令牌立即失效(登录状态吊销)。friends.created_time:好友申请创建时间,用于博主审批列表展示申请时间。
Languages
Python
50.2%
JavaScript
34.8%
CSS
7.5%
PowerShell
4%
Shell
3%
Other
0.5%