docs: 更新文档反映多用户系统,移除冗余钉钉通知
- README 补充多用户管理、JWT 认证、Web 管理配置等文档 - .env.example 添加 JWT_SECRET 配置 - 移除 notification.py(钉钉通知由 AutoPacking 处理) - 添加 CLAUDE.md 项目上下文
This commit is contained in:
parent
29a4dd91ad
commit
0ba16635df
@ -23,6 +23,10 @@ FRONTEND_PORT=3000
|
|||||||
ADMIN_USERNAME=admin
|
ADMIN_USERNAME=admin
|
||||||
ADMIN_PASSWORD=admin123
|
ADMIN_PASSWORD=admin123
|
||||||
|
|
||||||
|
# ---- JWT 认证 ----
|
||||||
|
# JWT 签名密钥(生产环境务必修改为随机字符串)
|
||||||
|
JWT_SECRET=ios-build-server-secret-key-change-in-production
|
||||||
|
|
||||||
# ---- 并发与清理 ----
|
# ---- 并发与清理 ----
|
||||||
# 最大并行打包数
|
# 最大并行打包数
|
||||||
MAX_CONCURRENT_BUILDS=2
|
MAX_CONCURRENT_BUILDS=2
|
||||||
|
|||||||
35
CLAUDE.md
Normal file
35
CLAUDE.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# iOS Build Server
|
||||||
|
|
||||||
|
FastAPI + Vue 3 iOS app auto-packaging service. Supports Ad_Hoc and App_Store builds with web UI, real-time logs, multi-user auth, and health monitoring.
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Backend**: Python 3.9+, FastAPI, SQLAlchemy (SQLite), JWT auth
|
||||||
|
- **Frontend**: Vue 3, Vite, Vue Router
|
||||||
|
- **Tests**: pytest (backend), vitest (frontend)
|
||||||
|
- **Deploy**: macOS launchd, deploy.sh management script
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./start.sh # Dev mode (backend + frontend hot reload)
|
||||||
|
./deploy.sh build # Install deps + build frontend
|
||||||
|
./deploy.sh start/stop/restart/status
|
||||||
|
./deploy.sh test # Run all tests (backend + frontend)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- `backend/routers/` — API routes: auth, users, config, apps, tasks
|
||||||
|
- `backend/services/` — build_service (packaging), build_queue (async queue), log_streamer (WebSocket)
|
||||||
|
- `backend/deps.py` — JWT auth dependency used by all protected routes
|
||||||
|
- `backend/config.py` — loads .env, defines all config constants
|
||||||
|
- `frontend/src/views/` — BuildView, HistoryView, ConfigView
|
||||||
|
- `config.json` — runtime config (apps, schemes, servers, upload, branches) managed via web UI
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- All backend routes require JWT auth except `/api/auth/login` and `/api/health`
|
||||||
|
- Admin-only routes: user management (`/api/users/*`)
|
||||||
|
- Config stored in `config.json` (apps/schemes/servers), `.env` (infra), SQLite `build_config` table (build settings)
|
||||||
|
- Chinese UI and documentation throughout
|
||||||
70
README.md
70
README.md
@ -1,6 +1,6 @@
|
|||||||
# iOS 自动打包服务
|
# iOS 自动打包服务
|
||||||
|
|
||||||
基于 FastAPI + Vue 3 的 iOS 应用自动打包服务,支持 Ad_Hoc 和 App_Store 两种打包类型,提供 Web 界面操作、实时日志流、构建历史管理和健康监测。
|
基于 FastAPI + Vue 3 的 iOS 应用自动打包服务,支持 Ad_Hoc 和 App_Store 两种打包类型,提供 Web 界面操作、实时日志流、构建历史管理、多用户权限和健康监测。
|
||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
@ -8,6 +8,7 @@
|
|||||||
- [快速开始](#快速开始)
|
- [快速开始](#快速开始)
|
||||||
- [配置说明](#配置说明)
|
- [配置说明](#配置说明)
|
||||||
- [服务管理](#服务管理)
|
- [服务管理](#服务管理)
|
||||||
|
- [多用户管理](#多用户管理)
|
||||||
- [Watchdog 健康监测](#watchdog-健康监测)
|
- [Watchdog 健康监测](#watchdog-健康监测)
|
||||||
- [macOS 开机自启](#macos-开机自启)
|
- [macOS 开机自启](#macos-开机自启)
|
||||||
- [开发模式](#开发模式)
|
- [开发模式](#开发模式)
|
||||||
@ -114,8 +115,9 @@ git clone -b main git@github.com:org/repo.git /path/to/ReadoorBranches/main
|
|||||||
| 变量 | 默认值 | 说明 |
|
| 变量 | 默认值 | 说明 |
|
||||||
|------|--------|------|
|
|------|--------|------|
|
||||||
| `BACKEND_PORT` | `8000` | 后端服务端口 |
|
| `BACKEND_PORT` | `8000` | 后端服务端口 |
|
||||||
| `ADMIN_USERNAME` | `admin` | 管理员用户名 |
|
| `ADMIN_USERNAME` | `admin` | 初始管理员用户名(首次启动自动创建) |
|
||||||
| `ADMIN_PASSWORD` | `admin123` | 管理员密码 |
|
| `ADMIN_PASSWORD` | `admin123` | 初始管理员密码 |
|
||||||
|
| `JWT_SECRET` | `ios-build-server-secret-key-change-in-production` | JWT 签名密钥(生产环境务必修改) |
|
||||||
|
|
||||||
### 打包配置
|
### 打包配置
|
||||||
|
|
||||||
@ -123,6 +125,20 @@ git clone -b main git@github.com:org/repo.git /path/to/ReadoorBranches/main
|
|||||||
|------|--------|------|
|
|------|--------|------|
|
||||||
| `MAX_CONCURRENT_BUILDS` | `2` | 最大并行打包数 |
|
| `MAX_CONCURRENT_BUILDS` | `2` | 最大并行打包数 |
|
||||||
| `BUILD_DIR_RETENTION_HOURS` | `24` | 打包目录保留时间(小时),超期自动清理 |
|
| `BUILD_DIR_RETENTION_HOURS` | `24` | 打包目录保留时间(小时),超期自动清理 |
|
||||||
|
| `BUILD_TIMEOUT_HOURS` | `1` | 打包超时时间(小时),超时自动标记失败 |
|
||||||
|
|
||||||
|
### Web 管理配置
|
||||||
|
|
||||||
|
以下配置通过管理后台 Web 界面管理(存储在 `config.json`):
|
||||||
|
|
||||||
|
| 配置项 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| App 管理 | 应用名称、Scheme、服务器环境、upload_key 等 |
|
||||||
|
| Scheme 管理 | 打包 Scheme 配置 |
|
||||||
|
| 服务器环境 | 测试/正式/自定义环境的 API 地址、Universal Link 等 |
|
||||||
|
| 分支管理 | 可打包的代码分支列表 |
|
||||||
|
| 版本号 | App_Ver 和 Build_Ver(写入 AutoPacking 脚本) |
|
||||||
|
| 上传配置 | OSS / WebDAV 文件上传设置 |
|
||||||
|
|
||||||
### Watchdog 配置
|
### Watchdog 配置
|
||||||
|
|
||||||
@ -194,6 +210,34 @@ logs/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 多用户管理
|
||||||
|
|
||||||
|
系统支持多用户管理,基于 JWT 认证。首次启动时自动创建 `.env` 中配置的管理员账号。
|
||||||
|
|
||||||
|
### 用户角色
|
||||||
|
|
||||||
|
| 角色 | 权限 |
|
||||||
|
|------|------|
|
||||||
|
| 管理员 | 所有功能 + 用户管理(增删改查、角色切换) |
|
||||||
|
| 普通用户 | 打包、查看历史、查看配置(不可修改配置、不可管理用户) |
|
||||||
|
|
||||||
|
### 管理用户
|
||||||
|
|
||||||
|
在管理后台「用户管理」页面(仅管理员可见):
|
||||||
|
|
||||||
|
- 创建用户:设置用户名、密码(至少 6 位)、是否管理员
|
||||||
|
- 修改密码:管理员可修改任意用户密码
|
||||||
|
- 切换角色:在管理员/普通用户之间切换
|
||||||
|
- 删除用户:不能删除自己
|
||||||
|
|
||||||
|
### 认证流程
|
||||||
|
|
||||||
|
1. `POST /api/auth/login` — 用户名+密码登录,返回 JWT token
|
||||||
|
2. 后续请求在 `Authorization: Bearer <token>` 头中携带 token
|
||||||
|
3. Token 有效期 24 小时,过期需重新登录
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Watchdog 健康监测
|
## Watchdog 健康监测
|
||||||
|
|
||||||
Watchdog 定期检查服务健康状态,发现异常自动重启。
|
Watchdog 定期检查服务健康状态,发现异常自动重启。
|
||||||
@ -326,9 +370,9 @@ cd frontend && npx vitest run
|
|||||||
| `test_build_queue.py` | 并发控制/任务执行/取消 | 7 |
|
| `test_build_queue.py` | 并发控制/任务执行/取消 | 7 |
|
||||||
| `test_edge_cases.py` | 边界情况/无效参数/配置备份 | 14 |
|
| `test_edge_cases.py` | 边界情况/无效参数/配置备份 | 14 |
|
||||||
| `App.test.js` | 登录/退出/导航 | 8 |
|
| `App.test.js` | 登录/退出/导航 | 8 |
|
||||||
| `BuildView.vue.test.js` | 打包表单/任务列表/提交 | 8 |
|
| `BuildView.test.js` | 打包表单/任务列表/提交 | 8 |
|
||||||
| `ConfigView.vue.test.js` | 配置管理/分支增删/设置保存 | 8 |
|
| `ConfigView.test.js` | 配置管理/分支增删/设置保存 | 8 |
|
||||||
| `HistoryView.vue.test.js` | 历史列表/过滤/下载/二维码 | 11 |
|
| `HistoryView.test.js` | 历史列表/过滤/下载/二维码 | 11 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -362,11 +406,13 @@ BuildServer/
|
|||||||
│ ├── main.py # 入口,路由注册,静态文件服务
|
│ ├── main.py # 入口,路由注册,静态文件服务
|
||||||
│ ├── config.py # 配置加载(读取 .env)
|
│ ├── config.py # 配置加载(读取 .env)
|
||||||
│ ├── database.py # SQLAlchemy 数据库初始化
|
│ ├── database.py # SQLAlchemy 数据库初始化
|
||||||
│ ├── models.py # 数据库模型(Task, BuildConfig)
|
│ ├── models.py # 数据库模型(Task, BuildConfig, User)
|
||||||
│ ├── schemas.py # Pydantic 请求/响应模型
|
│ ├── schemas.py # Pydantic 请求/响应模型
|
||||||
|
│ ├── deps.py # JWT 认证依赖
|
||||||
│ ├── routers/
|
│ ├── routers/
|
||||||
│ │ ├── auth.py # 登录认证
|
│ │ ├── auth.py # 登录认证(JWT)
|
||||||
│ │ ├── config.py # 配置管理(App/Scheme/Server CRUD)
|
│ │ ├── users.py # 用户管理(仅管理员)
|
||||||
|
│ │ ├── config.py # 配置管理(App/Scheme/Server/Upload/Branch/Version CRUD)
|
||||||
│ │ ├── apps.py # 打包选择(只读)
|
│ │ ├── apps.py # 打包选择(只读)
|
||||||
│ │ └── tasks.py # 任务管理(创建/列表/取消/下载)
|
│ │ └── tasks.py # 任务管理(创建/列表/取消/下载)
|
||||||
│ └── services/
|
│ └── services/
|
||||||
@ -456,13 +502,17 @@ tail -50 logs/server.log
|
|||||||
|
|
||||||
### Q: 如何修改管理员密码
|
### Q: 如何修改管理员密码
|
||||||
|
|
||||||
编辑 `.env`:
|
**方法一**:通过管理后台「用户管理」页面修改(推荐)
|
||||||
|
|
||||||
|
**方法二**:编辑 `.env` 后重启(仅影响初始管理员账号):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ADMIN_PASSWORD=new_password
|
ADMIN_PASSWORD=new_password
|
||||||
./deploy.sh restart
|
./deploy.sh restart
|
||||||
```
|
```
|
||||||
|
|
||||||
|
注意:`.env` 中的管理员账号仅在首次启动时创建,之后的密码修改请通过管理后台操作。
|
||||||
|
|
||||||
### Q: 多台 Mac 部署
|
### Q: 多台 Mac 部署
|
||||||
|
|
||||||
每台 Mac 上:
|
每台 Mac 上:
|
||||||
|
|||||||
@ -309,11 +309,6 @@ async def run_build_task(task_id: str):
|
|||||||
if task.oss_url:
|
if task.oss_url:
|
||||||
await log_streamer.emit(task_id, f"下载链接: {task.oss_url}")
|
await log_streamer.emit(task_id, f"下载链接: {task.oss_url}")
|
||||||
|
|
||||||
# 钉钉通知
|
|
||||||
from .notification import notify_build_result
|
|
||||||
dingtalk = full_config.get("upload", {}).get("dingtalk", {})
|
|
||||||
await notify_build_result(task, dingtalk)
|
|
||||||
|
|
||||||
# 带超时执行打包
|
# 带超时执行打包
|
||||||
await asyncio.wait_for(_do_build(), timeout=timeout_seconds)
|
await asyncio.wait_for(_do_build(), timeout=timeout_seconds)
|
||||||
|
|
||||||
|
|||||||
@ -1,93 +0,0 @@
|
|||||||
"""钉钉通知服务"""
|
|
||||||
import asyncio
|
|
||||||
import hashlib
|
|
||||||
import hmac
|
|
||||||
import base64
|
|
||||||
import time
|
|
||||||
import urllib.parse
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def _sign(secret: str) -> tuple[str, str]:
|
|
||||||
"""生成钉钉加签参数"""
|
|
||||||
timestamp = str(round(time.time() * 1000))
|
|
||||||
string_to_sign = f"{timestamp}\n{secret}"
|
|
||||||
hmac_code = hmac.new(
|
|
||||||
secret.encode("utf-8"),
|
|
||||||
string_to_sign.encode("utf-8"),
|
|
||||||
digestmod=hashlib.sha256,
|
|
||||||
).digest()
|
|
||||||
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
|
|
||||||
return timestamp, sign
|
|
||||||
|
|
||||||
|
|
||||||
async def send_dingtalk(webhook_url: str, title: str, text: str, secret: str = ""):
|
|
||||||
"""发送钉钉 Markdown 通知"""
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
url = webhook_url
|
|
||||||
if secret:
|
|
||||||
timestamp, sign = _sign(secret)
|
|
||||||
url = f"{url}×tamp={timestamp}&sign={sign}"
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"msgtype": "markdown",
|
|
||||||
"markdown": {"title": title, "text": text},
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
async with httpx.AsyncClient(timeout=10) as client:
|
|
||||||
resp = await client.post(url, json=payload)
|
|
||||||
data = resp.json()
|
|
||||||
if data.get("errcode") != 0:
|
|
||||||
logger.warning(f"钉钉通知发送失败: {data}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"钉钉通知异常: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
async def notify_build_result(task, dingtalk_config: dict):
|
|
||||||
"""发送打包结果通知"""
|
|
||||||
if not dingtalk_config.get("enabled") or not dingtalk_config.get("webhook_url"):
|
|
||||||
return
|
|
||||||
|
|
||||||
status = task.status
|
|
||||||
app_name = task.app_name
|
|
||||||
build_type = task.build_type
|
|
||||||
scheme_name = task.scheme_name
|
|
||||||
|
|
||||||
if status == "completed":
|
|
||||||
title = f"✅ {app_name} 打包成功"
|
|
||||||
lines = [
|
|
||||||
f"### ✅ {app_name} 打包成功",
|
|
||||||
f"- **应用**: {app_name}",
|
|
||||||
f"- **类型**: {build_type}",
|
|
||||||
f"- **Scheme**: {scheme_name}",
|
|
||||||
f"- **分支**: {task.branch}",
|
|
||||||
]
|
|
||||||
if task.oss_url:
|
|
||||||
lines.append(f"- **下载链接**: [点击下载]({task.oss_url})")
|
|
||||||
text = "\n".join(lines)
|
|
||||||
elif status == "failed":
|
|
||||||
title = f"❌ {app_name} 打包失败"
|
|
||||||
error = task.error_message or "未知错误"
|
|
||||||
lines = [
|
|
||||||
f"### ❌ {app_name} 打包失败",
|
|
||||||
f"- **应用**: {app_name}",
|
|
||||||
f"- **类型**: {build_type}",
|
|
||||||
f"- **Scheme**: {scheme_name}",
|
|
||||||
f"- **分支**: {task.branch}",
|
|
||||||
f"- **错误**: {error}",
|
|
||||||
]
|
|
||||||
text = "\n".join(lines)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
await send_dingtalk(
|
|
||||||
dingtalk_config["webhook_url"],
|
|
||||||
title,
|
|
||||||
text,
|
|
||||||
dingtalk_config.get("secret", ""),
|
|
||||||
)
|
|
||||||
Loading…
Reference in New Issue
Block a user