# 孤竹居士的个人博客 部署在阿里云 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(独立运行,不占用博客端口)。 ## 目录结构 ```text 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 ``` ## 快速开始(本地开发) 1. 安装依赖:`pip install -r requirements.txt` 2. 配置环境:复制 `.env.example` 为 `.env`,填写 `JWT_SECRET`、`BLOGGER_*`、`SMTP_*`。 3. 初始化数据库与博主:`python -m backend.seed` 4. 启动后端:`uvicorn backend.main:app --host 127.0.0.1 --port 8080` 5. 启动前端:任意静态服务器指向 `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 服务文件将在后续任务中补充(当前仅给出要点)。 1. 上传项目到 `/var/www/blog`,安装依赖:`pip install -r requirements.txt`。 2. 配置 `.env`(强随机 `JWT_SECRET`、博主账号、SMTP、安全限流)。 3. `python -m backend.seed` 初始化数据库与博主。 4. Nginx:80 端口托管 `frontend/` 静态文件;`/api/*` 反代 `127.0.0.1:8080`;`/uploads/*` 映射 `uploads/`;SPA 回退 `index.html`。 5. systemd 守护 `uvicorn`(开机自启、崩溃重启)。 6. 安全组仅开放 22 / 80 / 443 / 25565。 ## 数据库升级说明(仅旧库需要) 全新部署无需迁移:`python -m backend.seed` 会自动按最新结构建表。 若从旧版本升级(服务器上已有 `backend/blog.db`),需先补齐新字段再重启服务: ```bash # 服务器(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`:好友申请创建时间,用于博主审批列表展示申请时间。