序言,一些有的没的的介绍
- 用户认证 -- 对接老三账户的 OAuth 2.0,一套账号走天下
- 云同步 -- 班级数据的推送和拉取,带冲突检测,不会让你数据打架
- 快照管理 -- 就是云端存档,翻车了能回滚
- 订阅管理 -- 配合老三账户的服务订阅,分了几个等级来控制权限
技术栈就四个字(词?):Django + SQLite + Waitress + PyYAML,够用就行。
一、环境准备
系统要求
克隆仓库
git clone https://github.com/laosan577622/open_fuckseats.git
装依赖
进 backend/ 目录:
cd backend
pip install -r requirements.txt
一共就这么几个包:
| 包名 | 干嘛的 |
| Django | 不解释 |
| waitress | 生产用的 WSGI 服务器 |
| PyYAML | 读配置文件用的 |
| certifi | SSL 证书,OAuth 请求要用 |
| tzdata | 时区数据 |
二、创建 OAuth 应用
登录要走老三账户,所以你得先去那边注册个应用。
2.1 进服务管理
打开 老三账户开发者平台(点这里),登你的开发者账号。
2.2 创建应用
- 去 创建新应用
- 填一下:
- 应用名称:FuckSeats 云同步,或者你随便起一个
- 回调地址(Redirect URI):你后端的地址 +
/auth/oauth-callback,比如 https://你的域名/auth/oauth-callback
- 创建完了,把 Client ID 和 Client Secret 记下来
回调地址要跟你配置文件里 server.base_url + /auth/oauth-callback 完全一样,不然 OAuth 会回调失败。不过说实话你要是不填……可能也没啥事(但别怪我没提醒你)。
三、配置文件
3.1 先复制一份
cp cloud_config.example.yaml cloud_config.yaml
3.2 改配置
打开 cloud_config.yaml,照着改:
server:
# 对外访问地址,拼 OAuth 回调用的
# 生产环境记得改成你的真实域名
base_url: "https://你的域名"
host: "0.0.0.0"
port: 8000
# 生产环境一定要换掉,别用默认的
secret_key: "搞一个又长又随机的字符串放这"
debug: false
allowed_hosts: ["你的域名"]
timezone: "Asia/Shanghai"
metadata:
developer_name: "老三"
developer_website: "www.577622.xyz"
database:
# SQLite 数据库路径,相对于 backend/ 目录
name: "cloud.sqlite3"
laosan_oauth:
# 从老三账户开发者平台拿到的
client_id: "你的 Client ID"
client_secret: "你的 Client Secret"
authorize_url: "https://www.577622.xyz/oauth/authorize"
token_url: "https://www.577622.xyz/oauth/token"
userinfo_url: "https://www.577622.xyz/oauth/userinfo"
scope: "email nickname subscriptions"
3.3 几个重点说一下
server 部分
| 字段 | 说明 | 别踩的坑 |
base_url | 对外地址 | 拼回调 URL 用的,必须跟你注册应用时填的一致 |
secret_key | Django 密钥 | 生产环境必须改,建议 50 个字符以上,越乱越好 |
debug | 调试模式 | 上线了就关掉,设成 false |
allowed_hosts | 允许的域名 | 看你自己情况填 |
laosan_oauth 部分
| 字段 | 说明 |
client_id | 创建应用后给你的 |
client_secret | 应用密钥,别泄露了 |
scope | 要 email nickname subscriptions 这三个 |
subscription 部分
订阅配置默认给了 free、pro、pro_max 三档。要对接老三账户的服务订阅的话,每档里要填一个 service_identifier,对应你在老三账户那边创建的服务标识符。
| 字段 | 说明 |
fallback_tier | 匹配不上任何付费等级时默认用哪个,一般就 free |
tiers.xxx.display_name | 给用户看的等级名字 |
tiers.xxx.service_identifier | 老三账户那边的服务标识符,用来匹配用户订阅等级 |
tiers.xxx.price | 展示用的价格,是字符串,填啥都行,比如 "RMB 9999" |
tiers.xxx.purchase_url | 购买/管理链接,用户点"订阅"跳去的地址 |
tiers.xxx.limits | 各种限制,班级数量、快照数量、历史步数之类的 |
3.4 环境变量覆盖(可选)
上面那些关键参数都能用环境变量覆盖,Docker 或 CI/CD 的时候比较方便:
export CLOUD_BASE_URL="https://你的域名"
export CLOUD_SECRET_KEY="你的密钥"
export CLOUD_DEBUG="false"
export PORT=8000
export LAOSAN_OAUTH_CLIENT_ID="你的 Client ID"
export LAOSAN_OAUTH_CLIENT_SECRET="你的 Client Secret"
配置文件路径也能自定义:
export CLOUD_CONFIG_PATH="/path/to/your/cloud_config.yaml"
四、启动
4.1 生产模式
python run.py
启动后它会自己干这些事:读配置 → 跑数据库迁移 → 用 Waitress 把服务拉起来。
正常的话你会看到:
准备迁移云端数据库...
云端后端启动:http://0.0.0.0:8000
4.2 开发模式
python run.py -dev
用 Django 自带的开发服务器跑,改了代码自动重载,调试的时候用这个。
4.3 看看活没活
curl http://127.0.0.1:8000/health
返回这个就是正常的:
{"ok": true, "status": "success", "service": "fuckseats-cloud-backend"}
五、反向代理(生产环境搞一下)
上线了建议套个 Nginx,顺手把 HTTPS 也配了。
Nginx 配置参考
server {
listen 443 ssl;
server_name 你的域名;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name 你的域名;
return 301 https://$host$request_uri;
}
配完之后记得把 cloud_config.yaml 里的 base_url 改成 https:// 开头的。
六、API 接口
认证相关
| 方法 | 路径 | 干嘛的 | 要不要认证 |
| GET | /health | 健康检查 | 不用 |
| GET | /auth/login?callback=URL | 发起登录 | 不用 |
| GET | /auth/oauth-callback | OAuth 回调,后端自己处理 | 不用 |
| POST | /auth/exchange | 拿 session_code 换 token | 不用 |
| POST | /auth/logout | 登出 | Bearer Token |
用户信息
| 方法 | 路径 | 干嘛的 | 要不要认证 |
| GET | /api/me | 看看我是谁 | Bearer Token |
| POST | /api/me/refresh-subscription | 刷新订阅状态 | Bearer Token |
云同步
| 方法 | 路径 | 干嘛的 | 要不要认证 |
| GET | /api/sync/status | 同步状态 | Bearer Token |
| POST | /api/sync/push | 推数据上去 | Bearer Token |
| POST | /api/sync/push-batch | 批量推 | Bearer Token |
| GET | /api/sync/pull/{uuid} | 把数据拉下来 | Bearer Token |
| DELETE | /api/sync/{uuid} | 删掉云端的班级 | Bearer Token |
快照
| 方法 | 路径 | 干嘛的 | 要不要认证 |
| GET | /api/snapshots/{uuid} | 看有哪些快照 | Bearer Token |
| POST | /api/snapshots | 存一个快照 | Bearer Token |
| GET | /api/snapshots/{id}/download | 下载快照 | Bearer Token |
| DELETE | /api/snapshots/{id} | 删快照 | Bearer Token |
订阅
| 方法 | 路径 | 干嘛的 | 要不要认证 |
| GET | /api/subscription/plans | 看看有哪些订阅计划 | 不用 |
| POST | /api/subscription/redeem | 兑换码激活 | Bearer Token |
| GET | /api/subscription/purchase-url?tier=xxx | 拿购买链接 | 不用 |
七、OAuth 登录流程
整个登录是这么跑的:
客户端 后端 老三账户
| | |
|-- GET /auth/login ---------->| |
| ?callback=本地回调地址 | |
| |-- 302 跳过去 --------------->|
| | authorize_url?client_id=.. |
| | |
| |<-- 用户点了同意,回调回来 ----|
| | /auth/oauth-callback |
| | ?code=授权码&state=.. |
| | |
| |-- POST token_url ----------->|
| | 拿授权码换 access_token |
| | |
| |-- GET userinfo_url --------->|
| | 拿用户信息和订阅 |
| | |
|<-- 302 跳回 callback --------| |
| ?code=session_code | |
| | |
|-- POST /auth/exchange ------>| |
| {code: session_code} | |
| | |
|<-- session_token + 用户信息 -| |
拿到 token 之后,后面调需要认证的接口都在 Header 里带上:
Authorization: Bearer {session_token}
八、订阅等级配置
要对接老三账户的订阅功能的话,得先在那边创建服务,然后把标识符填回来。
具体步骤
- 去 老三账户服务管理 创建服务
- 把每个服务的 服务标识符 记下来
- 填到
cloud_config.yaml 的 subscription.tiers 里
subscription:
fallback_tier: "free"
tiers:
free:
display_name: "免费版"
limits:
max_classrooms: 3
sync_enabled: true
max_history_steps: 0
sync_ai_conversations: false
max_snapshots_per_classroom: 3
pro:
display_name: "Pro"
service_identifier: "你在老三账户那边创建的服务标识符"
price: "RMB 9999"
purchase_url: "https://www.577622.xyz/manage" # 这个等级单独的购买链接
limits:
max_classrooms: 10
sync_enabled: true
max_history_steps: 10
sync_ai_conversations: false
max_snapshots_per_classroom: 20
pro_max:
display_name: "Pro Max"
service_identifier: "另一个服务标识符"
price: "看你自己定"
purchase_url: "https://www.577622.xyz/manage"
limits:
max_classrooms: -1 # -1 就是不限
# ...
用户登录的时候后端会自动去老三账户的 userinfo 接口拿订阅信息,然后根据 service_identifier 匹配等级,不用你手动管。
九、限制和速率控制
这些都能在配置文件里调:
rate_limit:
sync_per_minute: 30 # 同步请求,一分钟最多 30 次
auth_per_minute_per_ip: 10 # 认证请求,一个 IP 一分钟 10 次
snapshot_per_hour: 20 # 快照操作,一小时 20 次
data_limits:
max_push_size_mb: 5 # 单次推送最大 5MB
max_batch_push_size_mb: 20 # 批量推送最大 20MB
session_token_ttl_days: 7 # Token 7 天过期
session_code_ttl_seconds: 60 # 登录换码 60 秒内有效
十、常见问题
OAuth 回调报 "登录状态不存在或已过期"
十有八九是 cloud_config.yaml 里的 server.base_url 跟你在老三账户那边填的回调地址对不上。后端拼回调 URL 的规则是 {base_url}/auth/oauth-callback,检查一下是不是一致的。
启动时报 PyYAML 相关的错
装一下就好了:
pip install PyYAML
或者设个环境变量,没装 PyYAML 的时候直接报错而不是默默跳过:
export CLOUD_REQUIRE_YAML=1
怎么生成 secret_key?
跑这个就行:
python -c "import secrets; print(secrets.token_urlsafe(50))"
怎么备份数据?
数据库就一个 SQLite 文件,直接把 backend/cloud.sqlite3 拷走就完事了。建议弄个 cron 定时备份。
Docker 部署
FROM python:3.12-slim
WORKDIR /app
COPY backend/ .
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 8000
CMD ["python", "run.py"]
跑的时候用环境变量传配置:
docker run -d \
-p 8000:8000 \
-e CLOUD_BASE_URL="https://你的域名" \
-e CLOUD_SECRET_KEY="你的密钥" \
-e LAOSAN_OAUTH_CLIENT_ID="你的 Client ID" \
-e LAOSAN_OAUTH_CLIENT_SECRET="你的 Client Secret" \
-v /data/fuckseats:/app/cloud.sqlite3 \
fuckseats-cloud
十一、上线前过一遍
客户端配置
打开设置,云服务器地址填你配置好的服务器地址就行
每日画饼:1p 和宝塔保姆级教程正在写,马上发