161 lines
8.1 KiB
Markdown
161 lines
8.1 KiB
Markdown
# 孤竹居士的个人博客
|
||
|
||
部署在阿里云 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`:好友申请创建时间,用于博主审批列表展示申请时间。
|