Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6c4b2313f | ||
|
|
f99bd336c9 |
@@ -1,222 +0,0 @@
|
||||
# 用户认证系统部署指南
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档描述如何在 Ubuntu 服务器上部署 ViGent2 用户认证系统。
|
||||
|
||||
| 组件 | 技术 | 说明 |
|
||||
|------|------|------|
|
||||
| 数据库 | Supabase (PostgreSQL) | 云端免费版 |
|
||||
| 认证 | FastAPI + JWT | HttpOnly Cookie |
|
||||
| 密码 | bcrypt | 单向哈希 |
|
||||
|
||||
---
|
||||
|
||||
## 步骤 1: 配置 Supabase
|
||||
|
||||
### 1.1 创建项目
|
||||
|
||||
1. 访问 [supabase.com](https://supabase.com)
|
||||
2. 创建免费项目
|
||||
3. 记录以下信息:
|
||||
- **Project URL**: `https://xxx.supabase.co`
|
||||
- **anon public key**: `eyJhbGciOiJIUzI1NiIs...`
|
||||
|
||||
### 1.2 创建数据库表
|
||||
|
||||
1. 进入 **SQL Editor**
|
||||
2. 执行以下 SQL:
|
||||
|
||||
```sql
|
||||
-- 1. 创建 users 表
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
username TEXT,
|
||||
role TEXT DEFAULT 'pending' CHECK (role IN ('pending', 'user', 'admin')),
|
||||
is_active BOOLEAN DEFAULT FALSE,
|
||||
expires_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 2. 创建 user_sessions 表
|
||||
CREATE TABLE IF NOT EXISTS user_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE UNIQUE,
|
||||
session_token TEXT UNIQUE NOT NULL,
|
||||
device_info TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 3. 创建 social_accounts 表
|
||||
CREATE TABLE IF NOT EXISTS social_accounts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
platform TEXT NOT NULL CHECK (platform IN ('bilibili', 'douyin', 'xiaohongshu')),
|
||||
logged_in BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
UNIQUE(user_id, platform)
|
||||
);
|
||||
|
||||
-- 4. 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON user_sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_social_user_platform ON social_accounts(user_id, platform);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 步骤 2: 配置后端环境变量
|
||||
|
||||
编辑 `/home/rongye/ProgramFiles/ViGent2/backend/.env`:
|
||||
|
||||
```env
|
||||
# =============== Supabase 配置 ===============
|
||||
SUPABASE_URL=https://your-project.supabase.co
|
||||
SUPABASE_KEY=eyJhbGciOiJIUzI1NiIs...
|
||||
|
||||
# =============== JWT 配置 ===============
|
||||
JWT_SECRET_KEY=随机生成的32位以上字符串
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_EXPIRE_HOURS=168 # 7天
|
||||
|
||||
# =============== 管理员配置 ===============
|
||||
ADMIN_EMAIL=admin@example.com
|
||||
ADMIN_PASSWORD=YourSecurePassword123!
|
||||
```
|
||||
|
||||
### 生成 JWT 密钥
|
||||
|
||||
```bash
|
||||
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 步骤 3: 安装依赖
|
||||
|
||||
```bash
|
||||
cd /home/rongye/ProgramFiles/ViGent2/backend
|
||||
source venv/bin/activate
|
||||
|
||||
pip install supabase python-jose[cryptography] passlib[bcrypt]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 步骤 4: 启动服务
|
||||
|
||||
```bash
|
||||
# 重启后端服务
|
||||
pm2 restart vigent2-backend
|
||||
```
|
||||
|
||||
首次启动时,管理员账号会自动创建。查看日志确认:
|
||||
|
||||
```bash
|
||||
pm2 logs vigent2-backend | grep "管理员"
|
||||
```
|
||||
|
||||
应该看到:`管理员账号已创建: admin@example.com`
|
||||
|
||||
---
|
||||
|
||||
## 步骤 5: 验证
|
||||
|
||||
### API 测试
|
||||
|
||||
```bash
|
||||
# 健康检查
|
||||
curl http://localhost:8006/health
|
||||
|
||||
# 注册测试
|
||||
curl -X POST http://localhost:8006/api/auth/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"test@example.com","password":"123456"}'
|
||||
|
||||
# 登录测试 (管理员)
|
||||
curl -X POST http://localhost:8006/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"admin@example.com","password":"YourSecurePassword123!"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 步骤 6: 防止 Supabase 7 天暂停
|
||||
|
||||
Supabase 免费版 7 天无活动会暂停。推荐使用服务器 crontab 方案。
|
||||
|
||||
### 方案 A: 服务器 crontab(推荐)
|
||||
|
||||
在 Ubuntu 服务器上执行:
|
||||
|
||||
```bash
|
||||
crontab -e
|
||||
```
|
||||
|
||||
添加以下行(每天凌晨 1 点执行):
|
||||
|
||||
```cron
|
||||
0 1 * * * curl -s -X GET "https://zcmitzlqlyzxlgwagouf.supabase.co/rest/v1/" -H "apikey: YOUR_SUPABASE_ANON_KEY" > /dev/null
|
||||
```
|
||||
|
||||
> 将 `YOUR_SUPABASE_ANON_KEY` 替换为实际的 anon key
|
||||
|
||||
### 方案 B: GitHub Actions
|
||||
|
||||
如果服务器可能长期关闭,可使用 GitHub Actions。
|
||||
|
||||
1. 创建独立仓库:`supabase-keep-alive`
|
||||
2. 上传 `.github/workflows/keep-supabase-alive.yml`
|
||||
3. 配置 Secrets:`SUPABASE_URL`, `SUPABASE_KEY`
|
||||
|
||||
> ⚠️ 需要 GitHub 账户有付款信息(免费计划也需要)
|
||||
|
||||
---
|
||||
|
||||
## 📁 文件结构
|
||||
|
||||
```
|
||||
backend/
|
||||
├── app/
|
||||
│ ├── api/
|
||||
│ │ ├── auth.py # 注册/登录/登出
|
||||
│ │ └── admin.py # 用户管理
|
||||
│ └── core/
|
||||
│ ├── supabase.py # Supabase 客户端
|
||||
│ ├── security.py # JWT + 密码
|
||||
│ ├── paths.py # Cookie 路径隔离
|
||||
│ └── deps.py # 认证依赖
|
||||
├── database/
|
||||
│ └── schema.sql # 数据库表定义
|
||||
└── user_data/ # 用户 Cookie (按 user_id 隔离)
|
||||
└── {user-uuid}/
|
||||
└── cookies/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 用户管理
|
||||
|
||||
### 在 Supabase Dashboard 中管理
|
||||
|
||||
1. 进入 **Table Editor > users**
|
||||
2. 激活用户:设置 `is_active = true`, `role = user`
|
||||
3. 设置过期时间:填写 `expires_at` 字段
|
||||
|
||||
### 使用 API 管理
|
||||
|
||||
需要管理员 Cookie:
|
||||
|
||||
```bash
|
||||
# 获取用户列表
|
||||
curl http://localhost:8006/api/admin/users -b "access_token=..."
|
||||
|
||||
# 激活用户 (30天有效期)
|
||||
curl -X POST http://localhost:8006/api/admin/users/{user_id}/activate \
|
||||
-H "Content-Type: application/json" \
|
||||
-b "access_token=..." \
|
||||
-d '{"expires_days": 30}'
|
||||
```
|
||||
@@ -251,6 +251,47 @@ pm2 stop vigent2-latentsync # 停止 LatentSync 服务
|
||||
pm2 delete all # 删除所有服务
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 步骤 10: 配置 Nginx HTTPS (可选 - 公网访问)
|
||||
|
||||
如果您需要通过公网域名 HTTPS 访问 (如 `https://vigent.hbyrkj.top`),请参考以下 Nginx 配置。
|
||||
|
||||
**前置条件**:
|
||||
1. 已申请 SSL 证书 (如 Let's Encrypt)。
|
||||
2. 使用 FRP 或其他方式将本地 3002 端口映射到服务器。
|
||||
|
||||
**配置示例** (`/etc/nginx/conf.d/vigent.conf`):
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your.domain.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name your.domain.com;
|
||||
|
||||
ssl_certificate /path/to/fullchain.pem;
|
||||
ssl_certificate_key /path/to/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3002; # 转发给 Next.js 前端
|
||||
|
||||
# 必须配置 WebSocket 支持,否则热更和即时通信失效
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 故障排除
|
||||
|
||||
122
Docs/DevLogs/Day10.md
Normal file
122
Docs/DevLogs/Day10.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
|
||||
## 🔧 隧道访问与视频播放修复 (11:00)
|
||||
|
||||
### 问题描述
|
||||
在通过 FRP 隧道 (如 `http://8.148.x.x:3002`) 访问时发现:
|
||||
1. **视频无法播放**:后端返回 404 (Not Found)。
|
||||
2. **发布页账号列表为空**:后端返回 500 (Internal Server Error)。
|
||||
|
||||
### 解决方案
|
||||
|
||||
#### 1. 视频播放修复
|
||||
- **后端 (`main.py`)**:这是根源问题。后端缺少 `uploads` 目录的挂载,导致静态资源无法访问。
|
||||
```python
|
||||
app.mount("/uploads", StaticFiles(directory=str(settings.UPLOAD_DIR)), name="uploads")
|
||||
```
|
||||
- **前端 (`next.config.ts`)**:添加反向代理规则,将 `/outputs` 和 `/uploads` 转发到后端端口 8006。
|
||||
```typescript
|
||||
{
|
||||
source: '/uploads/:path*',
|
||||
destination: 'http://localhost:8006/uploads/:path*',
|
||||
},
|
||||
{
|
||||
source: '/outputs/:path*',
|
||||
destination: 'http://localhost:8006/outputs/:path*',
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 账号列表 500 错误修复
|
||||
- **根源**:`backend/app/core/paths.py` 中的白名单缺少 `weixin` 和 `kuaishou`。
|
||||
- **现象**:当 `PublishService` 遍历所有平台时,遇到未在白名单的平台直接抛出 `ValueError`,导致整个接口崩溃。
|
||||
- **修复**:更新白名单。
|
||||
```python
|
||||
VALID_PLATFORMS: Set[str] = {"bilibili", "douyin", "xiaohongshu", "weixin", "kuaishou"}
|
||||
```
|
||||
|
||||
### 结果
|
||||
- ✅ 视频预览和历史视频均可正常播放。
|
||||
- ✅ 发布页账号列表恢复显示。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Nginx HTTPS 部署 (11:30)
|
||||
|
||||
### 需求
|
||||
用户在阿里云服务器上配置了 SSL 证书,需要通过 HTTPS 访问应用。
|
||||
|
||||
### 解决方案
|
||||
提供了 Nginx 配置文件 `nginx_vigent.conf`,配置了:
|
||||
1. **HTTP -> HTTPS 重定向**。
|
||||
2. **SSL 证书路径** (`/etc/letsencrypt/live/vigent.hbyrkj.top/...`)。
|
||||
3. **反向代理** 到本地 FRP 端口 (3002)。
|
||||
4. **WebSocket 支持** (用于 Next.js 热更和通信)。
|
||||
|
||||
### 结果
|
||||
- ✅ 用户可通过 `https://vigent.hbyrkj.top` 安全访问。
|
||||
- ✅ 代码自适应:前端 `API_BASE` 为空字符串,自动适配 HTTPS 协议,无需修改代码。
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI 细节优化 (11:45)
|
||||
|
||||
### 修改
|
||||
- 修改 `frontend/src/app/layout.tsx` 中的 Metadata。
|
||||
- 标题从 `Create Next App` 改为 `ViGent`。
|
||||
|
||||
### 结果
|
||||
- ✅ 浏览器标签页名称已更新。
|
||||
|
||||
---
|
||||
|
||||
## 🚪 用户登录退出功能 (12:00)
|
||||
|
||||
### 需求
|
||||
用户反馈没有退出的入口。
|
||||
|
||||
### 解决方案
|
||||
- **UI 修改**:在首页和发布管理页面的顶部导航栏添加红色的“退出”按钮 (位于最右侧)。
|
||||
- **逻辑实现**:
|
||||
```javascript
|
||||
onClick={async () => {
|
||||
if (confirm('确定要退出登录吗?')) {
|
||||
await fetch(`${API_BASE}/api/auth/logout`, { method: 'POST' });
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}}
|
||||
```
|
||||
- **部署**:已同步代码并重建前端。
|
||||
|
||||
---
|
||||
|
||||
## 🚢 Supabase 服务部署 (16:10)
|
||||
|
||||
### 需求
|
||||
由于需要多用户隔离和更完善的权限管理,决定从纯本地文件存储迁移到 Supabase BaaS 架构。
|
||||
|
||||
### 实施步骤
|
||||
|
||||
1. **Docker 部署 (Ubuntu)**
|
||||
- 使用官方 `docker-compose.yml`。
|
||||
- **端口冲突解决**:
|
||||
- `Moodist` 占用 4000 -> 迁移 Analytics 到 **4004**。
|
||||
- `code-server` 占用 8443 -> 迁移 Kong HTTPS 到 **8444**。
|
||||
- 自定义端口:Studio (**3003**), API (**8008**)。
|
||||
|
||||
2. **安全加固 (Aliyun Nginx)**
|
||||
- **双域名策略**:
|
||||
- `supabase.hbyrkj.top` -> Studio (3003)
|
||||
- `api.hbyrkj.top` -> API (8008)
|
||||
- **SSL**:配置 Let's Encrypt 证书。
|
||||
- **访问控制**:为 Studio 域名添加 `auth_basic` (htpasswd),防止未授权访问管理后台。
|
||||
- **WebSocket**:Nginx 配置 `Upgrade` 头支持 Realtime 功能。
|
||||
|
||||
3. **数据库初始化**
|
||||
- 使用 `backend/database/schema.sql` 初始化了 `users`, `social_accounts` 等表结构。
|
||||
|
||||
### 下一步计划 (Storage Migration)
|
||||
目前文件仍存储在本地磁盘,无法通过 RLS 进行隔离。
|
||||
**计划改造 LatentSync 流程**:
|
||||
1. 后端集成 Supabase Storage SDK。
|
||||
2. 实现 `Download (Storage) -> Local Process (LatentSync) -> Upload (Storage)` 闭环。
|
||||
3. 前端改为请求 Signed URL 进行播放。
|
||||
29
Docs/Logs.md
Normal file
29
Docs/Logs.md
Normal file
@@ -0,0 +1,29 @@
|
||||
rongye@r730-ubuntu:~/ProgramFiles/Supabase$ docker compose up -d
|
||||
[+] up 136/136
|
||||
✔ Image timberio/vector:0.28.1-alpine Pulled 63.3ss
|
||||
✔ Image supabase/storage-api:v1.33.0 Pulled 78.6ss
|
||||
✔ Image darthsim/imgproxy:v3.30.1 Pulled 151.9s
|
||||
✔ Image supabase/postgres-meta:v0.95.1 Pulled 87.5ss
|
||||
✔ Image supabase/logflare:1.27.0 Pulled 229.2s
|
||||
✔ Image supabase/postgres:15.8.1.085 Pulled 268.3s
|
||||
✔ Image supabase/supavisor:2.7.4 Pulled 101.6s
|
||||
✔ Image supabase/realtime:v2.68.0 Pulled 56.5ss
|
||||
✔ Image postgrest/postgrest:v14.1 Pulled 201.8s
|
||||
✔ Image supabase/edge-runtime:v1.69.28 Pulled 254.0s
|
||||
✔ Network supabase_default Created 0.1s
|
||||
✔ Volume supabase_db-config Created 0.1s
|
||||
✔ Container supabase-vector Healthy 16.9s
|
||||
✔ Container supabase-imgproxy Created 7.4s
|
||||
✔ Container supabase-db Healthy 20.6s
|
||||
✔ Container supabase-analytics Created 0.4s
|
||||
✔ Container supabase-edge-functions Created 1.8s
|
||||
✔ Container supabase-auth Created 1.7s
|
||||
✔ Container supabase-studio Created 2.0s
|
||||
✔ Container realtime-dev.supabase-realtime Created 1.7s
|
||||
✔ Container supabase-pooler Created 1.8s
|
||||
✔ Container supabase-kong Created 1.7s
|
||||
✔ Container supabase-meta Created 2.0s
|
||||
✔ Container supabase-rest Created 0.9s
|
||||
✔ Container supabase-storage Created 1.4s
|
||||
Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint supabase-analytics (2fd60a510a1f16bf29f8f5140f14ef457a284c5b65a2567b7be250a4f9708f34): failed to bind host port 0.0.0.0:4000/tcp: address already in use
|
||||
[ble: exit 1]
|
||||
210
Docs/SUPABASE_DEPLOY.md
Normal file
210
Docs/SUPABASE_DEPLOY.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# Supabase 全栈部署指南 (Infrastructure + Auth)
|
||||
|
||||
本文档涵盖了 Supabase 基础设施的 Docker 部署、密钥配置、Nginx 安全加固以及用户认证系统的数据库初始化。
|
||||
|
||||
---
|
||||
|
||||
## 第一部分:基础设施部署 (Infrastructure)
|
||||
|
||||
### 1. 准备 Docker 环境 (Ubuntu)
|
||||
|
||||
Supabase 严重依赖官方目录结构(挂载配置文件),**必须包含完整的 `docker` 目录**。
|
||||
|
||||
```bash
|
||||
# 1. 创建目录
|
||||
mkdir -p /home/rongye/ProgramFiles/Supabase
|
||||
cd /home/rongye/ProgramFiles/Supabase
|
||||
|
||||
# 2. 获取官方配置
|
||||
# 克隆仓库并提取 docker 目录
|
||||
git clone --depth 1 https://github.com/supabase/supabase.git temp_repo
|
||||
mv temp_repo/docker/* .
|
||||
rm -rf temp_repo
|
||||
|
||||
# 3. 复制环境变量模板
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### 2. 生成安全密钥
|
||||
|
||||
**警告**:官方模板使用的是公开的弱密钥。生产环境必须重新生成。
|
||||
使用项目提供的脚本自动生成全套强密钥:
|
||||
|
||||
```bash
|
||||
# 在 ViGent2 项目目录下
|
||||
cd /home/rongye/ProgramFiles/ViGent2/backend
|
||||
python generate_keys.py
|
||||
```
|
||||
|
||||
将脚本生成的输出(包括 `JWT_SECRET`, `ANON_KEY`, `SERVICE_ROLE_KEY` 等)复制并**覆盖** `/home/rongye/ProgramFiles/Supabase/.env` 中的对应内容。
|
||||
|
||||
### 3. 配置端口与冲突解决
|
||||
|
||||
编辑 Supabase 的 `.env` 文件,修改以下端口以避免与现有服务(Code-Server, Moodist)冲突:
|
||||
|
||||
```ini
|
||||
# --- Port Configuration ---
|
||||
# 避免与 Code-Server (8443) 冲突
|
||||
KONG_HTTPS_PORT=8444
|
||||
|
||||
# 自定义 API 端口 (默认 8000)
|
||||
KONG_HTTP_PORT=8008
|
||||
|
||||
# 自定义管理后台端口 (默认 3000)
|
||||
STUDIO_PORT=3003
|
||||
|
||||
# 外部访问 URL (重要:填入你的公网 API 域名/IP)
|
||||
# 如果配置了 Nginx 反代: https://api.hbyrkj.top
|
||||
# 如果直连: http://8.148.25.142:8008
|
||||
API_EXTERNAL_URL=https://api.hbyrkj.top
|
||||
```
|
||||
|
||||
### 4. 启动服务
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第二部分:安全访问配置 (Nginx)
|
||||
|
||||
建议在阿里云公网网关上配置 Nginx 反向代理,通过 Frp 隧道连接内网服务。
|
||||
|
||||
### 1. 域名规划
|
||||
- **管理后台**: `https://supabase.hbyrkj.top` -> 内网 3003
|
||||
- **API 接口**: `https://api.hbyrkj.top` -> 内网 8008
|
||||
|
||||
### 2. Nginx 配置示例
|
||||
|
||||
```nginx
|
||||
# Studio (需要密码保护)
|
||||
server {
|
||||
server_name supabase.hbyrkj.top;
|
||||
|
||||
# SSL 配置略...
|
||||
|
||||
location / {
|
||||
# Basic Auth 保护后台
|
||||
auth_basic "Restricted Studio";
|
||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
|
||||
proxy_pass http://127.0.0.1:3003;
|
||||
|
||||
# WebSocket 支持 (Realtime 必须)
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
|
||||
# API (公开访问)
|
||||
server {
|
||||
server_name api.hbyrkj.top;
|
||||
|
||||
# SSL 配置略...
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8008;
|
||||
|
||||
# 允许 WebSocket
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第三部分:数据库与认证配置 (Database & Auth)
|
||||
|
||||
### 1. 初始化表结构 (Schema)
|
||||
|
||||
访问管理后台 (Studio) 的 **SQL Editor**,执行以下 SQL 来初始化 ViGent2 所需的表结构:
|
||||
|
||||
```sql
|
||||
-- 1. 用户表 (扩展 auth.users 或独立存储)
|
||||
-- 注意:这里使用独立表设计,与 FastAPI 逻辑解耦
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
username TEXT,
|
||||
role TEXT DEFAULT 'pending' CHECK (role IN ('pending', 'user', 'admin')),
|
||||
is_active BOOLEAN DEFAULT FALSE,
|
||||
expires_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 2. 会话表 (单设备登录控制)
|
||||
CREATE TABLE IF NOT EXISTS user_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE UNIQUE,
|
||||
session_token TEXT UNIQUE NOT NULL,
|
||||
device_info TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 3. 社交媒体账号绑定表
|
||||
CREATE TABLE IF NOT EXISTS social_accounts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
platform TEXT NOT NULL CHECK (platform IN ('bilibili', 'douyin', 'xiaohongshu')),
|
||||
logged_in BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
UNIQUE(user_id, platform)
|
||||
);
|
||||
|
||||
-- 4. 性能索引
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON user_sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_social_user_platform ON social_accounts(user_id, platform);
|
||||
```
|
||||
|
||||
### 2. 后端集成配置 (FastAPI)
|
||||
|
||||
修改 `ViGent2/backend/.env` 以连接到自托管的 Supabase:
|
||||
|
||||
```ini
|
||||
# =============== Supabase 配置 ===============
|
||||
# 指向 Docker 部署的 API 端口 (内网直连推荐用 Localhost)
|
||||
SUPABASE_URL=http://localhost:8008
|
||||
|
||||
# 使用生成的 SERVICE_ROLE_KEY (后端需要管理员权限)
|
||||
SUPABASE_KEY=eyJhbGciOiJIUzI1Ni...
|
||||
|
||||
# =============== JWT 配置 ===============
|
||||
# 必须与 Supabase .env 中的 JWT_SECRET 保持一致!
|
||||
JWT_SECRET_KEY=填入_generate_keys.py_生成的_JWT_SECRET
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_EXPIRE_HOURS=168
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第四部分:常用维护命令
|
||||
|
||||
**查看服务状态**:
|
||||
```bash
|
||||
cd /home/rongye/ProgramFiles/Supabase
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
**查看密钥**:
|
||||
```bash
|
||||
grep -E "ANON|SERVICE|SECRET" .env
|
||||
```
|
||||
|
||||
**重启服务**:
|
||||
```bash
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
**完全重置数据库 (慎用)**:
|
||||
```bash
|
||||
docker compose down -v
|
||||
rm -rf volumes/db/data
|
||||
docker compose up -d
|
||||
```
|
||||
@@ -141,12 +141,12 @@ backend/
|
||||
|
||||
| 端点 | 方法 | 功能 |
|
||||
|------|------|------|
|
||||
| `/api/materials` | POST | 上传素材视频 |
|
||||
| `/api/materials` | GET | 获取素材列表 |
|
||||
| `/api/videos/generate` | POST | 创建视频生成任务 |
|
||||
| `/api/tasks/{id}` | GET | 查询任务状态 |
|
||||
| `/api/videos/{id}/download` | GET | 下载生成的视频 |
|
||||
| `/api/publish` | POST | 发布到社交平台 |
|
||||
| `/api/materials` | POST | 上传素材视频 | ✅ |
|
||||
| `/api/materials` | GET | 获取素材列表 | ✅ |
|
||||
| `/api/videos/generate` | POST | 创建视频生成任务 | ✅ |
|
||||
| `/api/tasks/{id}` | GET | 查询任务状态 | ✅ |
|
||||
| `/api/videos/{id}/download` | GET | 下载生成的视频 | ✅ |
|
||||
| `/api/publish` | POST | 发布到社交平台 | ✅ |
|
||||
|
||||
#### 2.3 Celery 任务定义
|
||||
|
||||
@@ -221,7 +221,7 @@ cp -r SuperIPAgent/social-auto-upload backend/social_upload
|
||||
| **声音克隆** | 集成 GPT-SoVITS,用自己的声音 |
|
||||
| **批量生成** | 上传 Excel/CSV,批量生成视频 |
|
||||
| **字幕编辑器** | 可视化调整字幕样式、位置 |
|
||||
| **Docker 部署** | 一键部署到云服务器 |
|
||||
| **Docker 部署** | 一键部署到云服务器 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
@@ -295,6 +295,34 @@ cp -r SuperIPAgent/social-auto-upload backend/social_upload
|
||||
- [x] 超时保护 (消除无限循环)
|
||||
- [x] 完整类型提示
|
||||
|
||||
### 阶段十四:用户认证系统 (Day 9) ✅
|
||||
|
||||
> **目标**:实现安全、隔离的多用户认证体系
|
||||
|
||||
- [x] Supabase 云数据库集成 (本地自托管)
|
||||
- [x] JWT + HttpOnly Cookie 认证架构
|
||||
- [x] 用户表与权限表设计 (RLS 准备)
|
||||
- [x] 认证部署文档 (Docs/SUPABASE_DEPLOY.md)
|
||||
|
||||
### 阶段十五:部署稳定性优化 (Day 9) ✅
|
||||
|
||||
> **目标**:确保生产环境服务长期稳定
|
||||
|
||||
- [x] 依赖冲突修复 (bcrypt)
|
||||
- [x] 前端构建修复 (Production Build)
|
||||
- [x] PM2 进程守护配置
|
||||
- [x] 部署手册更新 (Docs/DEPLOY_MANUAL.md)
|
||||
|
||||
### 阶段十六:HTTPS 全栈部署 (Day 10) ✅
|
||||
|
||||
> **目标**:实现安全的公网 HTTPS 访问
|
||||
|
||||
- [x] 阿里云 Nginx 反向代理配置
|
||||
- [x] Let's Encrypt SSL 证书集成
|
||||
- [x] Supabase 自托管部署 (Docker)
|
||||
- [x] 端口冲突解决 (3003/8008/8444)
|
||||
- [x] Basic Auth 管理后台保护
|
||||
|
||||
---
|
||||
|
||||
## 项目目录结构 (最终)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
**项目**:ViGent2 数字人口播视频生成系统
|
||||
**服务器**:Dell R730 (2× RTX 3090 24GB)
|
||||
**更新时间**:2026-01-23
|
||||
**整体进度**:100%(Day 9 部署稳定性优化完成)
|
||||
**更新时间**:2026-01-26
|
||||
**整体进度**:100%(Day 10 HTTPS 部署与细节完善)
|
||||
|
||||
## 📖 快速导航
|
||||
|
||||
@@ -138,6 +138,16 @@
|
||||
- [x] 部署服务自愈 (PM2 配置优化)
|
||||
- [x] 部署手册全量更新 (DEPLOY_MANUAL.md)
|
||||
|
||||
### 阶段十六:HTTPS 部署与细节完善 (Day 10)
|
||||
- [x] 隧道访问修复 (StaticFiles 挂载 + Rewrite)
|
||||
- [x] 平台账号列表 500 错误修复 (paths.py)
|
||||
- [x] Nginx HTTPS 配置 (反向代理 + SSL)
|
||||
- [x] 浏览器标题修改 (ViGent)
|
||||
- [x] 代码自适应 HTTPS 验证
|
||||
- [x] **Supabase 自托管部署** (Docker, 3003/8008端口)
|
||||
- [x] **安全加固** (Basic Auth 保护后台)
|
||||
- [x] **端口冲突解决** (迁移 Analytics/Kong)
|
||||
|
||||
---
|
||||
|
||||
## 🛤️ 后续规划
|
||||
@@ -301,5 +311,11 @@ Day 9: 发布模块优化 ✅ 完成
|
||||
- 前端生产构建流程修复
|
||||
- LatentSync 严重卡顿修复 (线程数限制)
|
||||
- 部署手册全量更新
|
||||
|
||||
Day 10: HTTPS 部署与细节完善 ✅ 完成
|
||||
- 隧道访问视频修正 (挂载 uploads)
|
||||
- 账号列表 Bug 修复 (paths.py 白名单)
|
||||
- 阿里云 Nginx HTTPS 部署
|
||||
- UI 细节优化 (Title 更新)
|
||||
```
|
||||
|
||||
|
||||
19
README.md
19
README.md
@@ -21,7 +21,7 @@
|
||||
|------|------|
|
||||
| 前端 | Next.js 14 + TypeScript + TailwindCSS |
|
||||
| 后端 | FastAPI + Python 3.10 |
|
||||
| 数据库 | **Supabase** (PostgreSQL) + Redis |
|
||||
| 数据库 | **Supabase** (PostgreSQL) Local Docker |
|
||||
| 认证 | **JWT** + HttpOnly Cookie |
|
||||
| 唇形同步 | **LatentSync 1.6** (Latent Diffusion, 512×512) |
|
||||
| TTS | EdgeTTS |
|
||||
@@ -133,12 +133,13 @@ nohup python -m scripts.server > server.log 2>&1 &
|
||||
|
||||
## 🌐 访问地址
|
||||
|
||||
| 服务 | 地址 |
|
||||
|------|------|
|
||||
| 视频生成 | http://服务器IP:3002 |
|
||||
| 发布管理 | http://服务器IP:3002/publish |
|
||||
| API 文档 | http://服务器IP:8006/docs |
|
||||
| 模型API | http://服务器IP:8007/docs |
|
||||
| 服务 | 地址 | 说明 |
|
||||
|------|------|------|
|
||||
| **视频生成 (UI)** | `https://vigent.hbyrkj.top` | 用户访问入口 |
|
||||
| **API 服务** | `http://<服务器IP>:8006` | 后端 Swagger |
|
||||
| **认证管理 (Studio)** | `https://supabase.hbyrkj.top` | 需要 Basic Auth |
|
||||
| **认证 API (Kong)** | `https://api.hbyrkj.top` | Supabase 接口 |
|
||||
| **模型服务** | `http://<服务器IP>:8007` | LatentSync |
|
||||
|
||||
---
|
||||
|
||||
@@ -146,7 +147,9 @@ nohup python -m scripts.server > server.log 2>&1 &
|
||||
|
||||
- [LatentSync 部署指南](models/LatentSync/DEPLOY.md)
|
||||
- [手动部署指南](Docs/DEPLOY_MANUAL.md)
|
||||
- [认证部署指南](Docs/AUTH_DEPLOY.md)
|
||||
- [LatentSync 部署指南](models/LatentSync/DEPLOY.md)
|
||||
- [手动部署指南](Docs/DEPLOY_MANUAL.md)
|
||||
- [Supabase 部署指南](Docs/SUPABASE_DEPLOY.md)
|
||||
- [开发日志](Docs/DevLogs/)
|
||||
- [任务进度](Docs/task_complete.md)
|
||||
|
||||
|
||||
@@ -13,9 +13,8 @@ DEFAULT_TTS_VOICE=zh-CN-YunxiNeural
|
||||
|
||||
# =============== LatentSync 配置 ===============
|
||||
# GPU 选择 (0=第一块GPU, 1=第二块GPU)
|
||||
LATENTSYNC_GPU_ID=1
|
||||
LATENTSYNC_GPU_ID=0
|
||||
|
||||
# 使用本地模式 (true) 或远程 API (false)
|
||||
# 使用本地模式 (true) 或远程 API (false)
|
||||
LATENTSYNC_LOCAL=true
|
||||
|
||||
@@ -35,7 +34,7 @@ LATENTSYNC_GUIDANCE_SCALE=1.5
|
||||
LATENTSYNC_ENABLE_DEEPCACHE=true
|
||||
|
||||
# 随机种子 (设为 -1 则随机)
|
||||
LATENTSYNC_SEED=1247
|
||||
LATENTSYNC_SEED=-1
|
||||
|
||||
# =============== 上传配置 ===============
|
||||
# 最大上传文件大小 (MB)
|
||||
@@ -47,16 +46,16 @@ MAX_UPLOAD_SIZE_MB=500
|
||||
|
||||
# =============== Supabase 配置 ===============
|
||||
# 从 Supabase 项目设置 > API 获取
|
||||
SUPABASE_URL=https://zcmitzlqlyzxlgwagouf.supabase.co
|
||||
SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InpjbWl0emxxbHl6eGxnd2Fnb3VmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkxMzkwNzEsImV4cCI6MjA4NDcxNTA3MX0.2NNkkR0cowopcsCs5bP-DTCksiOuqNjmhfyXGmLdTrM
|
||||
SUPABASE_URL=your_supabase_url_here
|
||||
SUPABASE_KEY=your_supabase_anon_key_here
|
||||
|
||||
# =============== JWT 配置 ===============
|
||||
# 用于签名 JWT Token 的密钥 (请更换为随机字符串)
|
||||
JWT_SECRET_KEY=F4MagRkf7nJsN-ag9AB7Q-30MbZRe7Iu4E9p9xRzyic
|
||||
JWT_SECRET_KEY=generate_your_secure_random_key_here
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_EXPIRE_HOURS=168
|
||||
|
||||
# =============== 管理员配置 ===============
|
||||
# 服务启动时自动创建的管理员账号
|
||||
ADMIN_EMAIL=
|
||||
ADMIN_PASSWORD=
|
||||
ADMIN_EMAIL=admin@example.com
|
||||
ADMIN_PASSWORD=change_this_password_immediately
|
||||
|
||||
@@ -19,7 +19,7 @@ def sanitize_filename(filename: str) -> str:
|
||||
return safe_name
|
||||
|
||||
|
||||
@router.post("/")
|
||||
@router.post("")
|
||||
async def upload_material(file: UploadFile = File(...)):
|
||||
if not file.filename.lower().endswith(('.mp4', '.mov', '.avi')):
|
||||
raise HTTPException(400, "Invalid format")
|
||||
@@ -47,7 +47,7 @@ async def upload_material(file: UploadFile = File(...)):
|
||||
"type": "video"
|
||||
}
|
||||
|
||||
@router.get("/")
|
||||
@router.get("")
|
||||
async def list_materials():
|
||||
materials_dir = settings.UPLOAD_DIR / "materials"
|
||||
files = []
|
||||
|
||||
@@ -46,7 +46,7 @@ def _get_user_id(request: Request) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
@router.post("/", response_model=PublishResponse)
|
||||
@router.post("", response_model=PublishResponse)
|
||||
async def publish_video(request: PublishRequest, req: Request, background_tasks: BackgroundTasks):
|
||||
"""发布视频到指定平台"""
|
||||
# Validate platform
|
||||
|
||||
@@ -10,7 +10,7 @@ BASE_DIR = Path(__file__).parent.parent.parent
|
||||
USER_DATA_DIR = BASE_DIR / "user_data"
|
||||
|
||||
# 有效的平台列表
|
||||
VALID_PLATFORMS: Set[str] = {"bilibili", "douyin", "xiaohongshu"}
|
||||
VALID_PLATFORMS: Set[str] = {"bilibili", "douyin", "xiaohongshu", "weixin", "kuaishou"}
|
||||
|
||||
# UUID 格式正则
|
||||
UUID_PATTERN = re.compile(r'^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$', re.IGNORECASE)
|
||||
|
||||
@@ -101,7 +101,7 @@ def set_auth_cookie(response: Response, token: str) -> None:
|
||||
key="access_token",
|
||||
value=token,
|
||||
httponly=True,
|
||||
secure=True, # 生产环境使用 HTTPS
|
||||
secure=not settings.DEBUG, # 开发/测试环境(DEBUG=True)允许非HTTPS
|
||||
samesite="lax",
|
||||
max_age=settings.JWT_EXPIRE_HOURS * 3600
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ settings.OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
(settings.UPLOAD_DIR / "materials").mkdir(exist_ok=True)
|
||||
|
||||
app.mount("/outputs", StaticFiles(directory=str(settings.OUTPUT_DIR)), name="outputs")
|
||||
app.mount("/uploads", StaticFiles(directory=str(settings.UPLOAD_DIR)), name="uploads")
|
||||
|
||||
# 注册路由
|
||||
app.include_router(materials.router, prefix="/api/materials", tags=["Materials"])
|
||||
|
||||
93
backend/generate_keys.py
Normal file
93
backend/generate_keys.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import secrets
|
||||
import string
|
||||
|
||||
def generate_secure_secret(length=64):
|
||||
"""生成安全的随机十六进制字符串"""
|
||||
return secrets.token_hex(length // 2)
|
||||
|
||||
def generate_random_string(length=32):
|
||||
"""生成包含字母数字的随机字符串 (用于密码等)"""
|
||||
chars = string.ascii_letters + string.digits
|
||||
return ''.join(secrets.choice(chars) for _ in range(length))
|
||||
|
||||
def base64url_encode(input_bytes):
|
||||
return base64.urlsafe_b64encode(input_bytes).decode('utf-8').rstrip('=')
|
||||
|
||||
def generate_jwt(role, secret):
|
||||
# 1. Header
|
||||
header = {
|
||||
"alg": "HS256",
|
||||
"typ": "JWT"
|
||||
}
|
||||
|
||||
# 2. Payload
|
||||
now = int(time.time())
|
||||
payload = {
|
||||
"role": role,
|
||||
"iss": "supabase",
|
||||
"iat": now,
|
||||
"exp": now + 315360000 # 10年有效期
|
||||
}
|
||||
|
||||
# Encode parts
|
||||
header_b64 = base64url_encode(json.dumps(header).encode('utf-8'))
|
||||
payload_b64 = base64url_encode(json.dumps(payload).encode('utf-8'))
|
||||
|
||||
# 3. Signature
|
||||
signing_input = f"{header_b64}.{payload_b64}".encode('utf-8')
|
||||
signature = hmac.new(
|
||||
secret.encode('utf-8'),
|
||||
signing_input,
|
||||
hashlib.sha256
|
||||
).digest()
|
||||
signature_b64 = base64url_encode(signature)
|
||||
|
||||
return f"{header_b64}.{payload_b64}.{signature_b64}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("🔐 Supabase 全自动配置生成器 (Zero Dependency)")
|
||||
print("=" * 60)
|
||||
print("正在生成所有密钥...\n")
|
||||
|
||||
# 1. 自动生成主密钥
|
||||
jwt_secret = generate_secure_secret(64)
|
||||
|
||||
# 2. 基于主密钥生成 JWT
|
||||
anon_key = generate_jwt("anon", jwt_secret)
|
||||
service_key = generate_jwt("service_role", jwt_secret)
|
||||
|
||||
# 3. 生成其他加密 Key和密码
|
||||
vault_key = generate_secure_secret(32)
|
||||
meta_key = generate_secure_secret(32)
|
||||
secret_key_base = generate_secure_secret(64)
|
||||
|
||||
db_password = generate_random_string(20)
|
||||
dashboard_password = generate_random_string(16)
|
||||
|
||||
# 4. 输出结果
|
||||
print(f"✅ 生成完成!请直接复制以下内容覆盖您的 .env 文件中的对应部分:\n")
|
||||
|
||||
print("-" * 20 + " [ 复制开始 ] " + "-" * 20)
|
||||
print(f"# === 数据库安全配置 ===")
|
||||
print(f"POSTGRES_PASSWORD={db_password}")
|
||||
print(f"JWT_SECRET={jwt_secret}")
|
||||
print(f"ANON_KEY={anon_key}")
|
||||
print(f"SERVICE_ROLE_KEY={service_key}")
|
||||
print(f"SECRET_KEY_BASE={secret_key_base}")
|
||||
print(f"VAULT_ENC_KEY={vault_key}")
|
||||
print(f"PG_META_CRYPTO_KEY={meta_key}")
|
||||
print(f"\n# === 管理后台配置 ===")
|
||||
print(f"DASHBOARD_USERNAME=admin")
|
||||
print(f"DASHBOARD_PASSWORD={dashboard_password}")
|
||||
print("-" * 20 + " [ 复制结束 ] " + "-" * 20)
|
||||
|
||||
print("\n💡 提示:")
|
||||
print(f"1. 数据库密码: {db_password}")
|
||||
print(f"2. 后台登录密码: {dashboard_password}")
|
||||
print("请妥善保管这些密码!")
|
||||
@@ -8,6 +8,14 @@ const nextConfig: NextConfig = {
|
||||
source: '/api/:path*',
|
||||
destination: 'http://localhost:8006/api/:path*', // 服务器本地代理
|
||||
},
|
||||
{
|
||||
source: '/uploads/:path*',
|
||||
destination: 'http://localhost:8006/uploads/:path*', // 转发上传的素材
|
||||
},
|
||||
{
|
||||
source: '/outputs/:path*',
|
||||
destination: 'http://localhost:8006/outputs/:path*', // 转发生成的视频
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,7 +4,9 @@ import { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { getCurrentUser, User } from '@/lib/auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8006';
|
||||
const API_BASE = typeof window === 'undefined'
|
||||
? (process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8006')
|
||||
: '';
|
||||
|
||||
interface UserListItem {
|
||||
id: string;
|
||||
|
||||
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "ViGent",
|
||||
description: "ViGent Talking Head Agent",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
// 动态获取 API 地址:服务端使用 localhost,客户端使用当前域名
|
||||
const API_BASE = typeof window !== 'undefined'
|
||||
? `http://${window.location.hostname}:8006`
|
||||
: 'http://localhost:8006';
|
||||
const API_BASE = typeof window === 'undefined'
|
||||
? 'http://localhost:8006'
|
||||
: '';
|
||||
|
||||
// 类型定义
|
||||
interface Material {
|
||||
@@ -73,7 +72,7 @@ export default function Home() {
|
||||
setDebugData("Loading...");
|
||||
|
||||
// Add timestamp to prevent caching
|
||||
const url = `${API_BASE}/api/materials/?t=${new Date().getTime()}`;
|
||||
const url = `${API_BASE}/api/materials?t=${new Date().getTime()}`;
|
||||
const res = await fetch(url);
|
||||
|
||||
if (!res.ok) {
|
||||
@@ -197,7 +196,7 @@ export default function Home() {
|
||||
setUploadError('网络错误,上传失败');
|
||||
};
|
||||
|
||||
xhr.open('POST', `${API_BASE}/api/materials/`);
|
||||
xhr.open('POST', `${API_BASE}/api/materials`);
|
||||
xhr.send(formData);
|
||||
|
||||
// 清空 input 以便可以再次选择同一文件
|
||||
@@ -299,6 +298,21 @@ export default function Home() {
|
||||
>
|
||||
发布管理
|
||||
</Link>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (confirm('确定要退出登录吗?')) {
|
||||
try {
|
||||
await fetch(`${API_BASE}/api/auth/logout`, { method: 'POST' });
|
||||
window.location.href = '/login';
|
||||
} catch (e) {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="px-4 py-2 bg-red-500/10 hover:bg-red-500/20 text-red-200 rounded-lg transition-colors"
|
||||
>
|
||||
退出
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -7,9 +7,9 @@ const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
||||
import Link from "next/link";
|
||||
|
||||
// 动态获取 API 地址:服务端使用 localhost,客户端使用当前域名
|
||||
const API_BASE = typeof window !== 'undefined'
|
||||
? `http://${window.location.hostname}:8006`
|
||||
: 'http://localhost:8006';
|
||||
const API_BASE = typeof window === 'undefined'
|
||||
? 'http://localhost:8006'
|
||||
: '';
|
||||
|
||||
interface Account {
|
||||
platform: string;
|
||||
@@ -95,7 +95,7 @@ export default function PublishPage() {
|
||||
|
||||
for (const platform of selectedPlatforms) {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/publish/`, {
|
||||
const res = await fetch(`${API_BASE}/api/publish`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
@@ -258,11 +258,26 @@ export default function PublishPage() {
|
||||
href="/"
|
||||
className="px-4 py-2 bg-white/10 hover:bg-white/20 text-white rounded-lg transition-colors"
|
||||
>
|
||||
视频生成
|
||||
返回创作
|
||||
</Link>
|
||||
<span className="px-4 py-2 bg-gradient-to-r from-purple-600 to-pink-600 text-white rounded-lg font-semibold">
|
||||
发布管理
|
||||
</span>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (confirm('确定要退出登录吗?')) {
|
||||
try {
|
||||
await fetch(`${API_BASE}/api/auth/logout`, { method: 'POST' });
|
||||
window.location.href = '/login';
|
||||
} catch (e) {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="px-4 py-2 bg-red-500/10 hover:bg-red-500/20 text-red-200 rounded-lg transition-colors"
|
||||
>
|
||||
退出
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
* 认证工具函数
|
||||
*/
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8006';
|
||||
const API_BASE = typeof window === 'undefined'
|
||||
? (process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8006')
|
||||
: '';
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user