Files
ViGent2/Docs/DevLogs/Day15.md
Kevin Wong 6e58f4bbe7 更新
2026-02-02 17:16:07 +08:00

10 KiB
Raw Blame History

Day 15 - 手机号登录迁移 + 账户设置功能

日期2026-02-02


🔐 认证系统迁移:邮箱 → 手机号

背景

根据业务需求将用户认证从邮箱登录迁移到手机号登录11位中国手机号

变更范围

组件 变更内容
数据库 Schema email 字段替换为 phone
后端 API 注册/登录/获取用户信息接口使用 phone
前端页面 登录/注册页面改为手机号输入框
管理员配置 ADMIN_EMAIL 改为 ADMIN_PHONE

📦 后端修改

1. 数据库 Schema (schema.sql)

文件: backend/database/schema.sql

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    phone TEXT UNIQUE NOT NULL,  -- 原 email 改为 phone
    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()
);

CREATE INDEX idx_users_phone ON users(phone);

2. 认证 API (auth.py)

文件: backend/app/api/auth.py

请求模型更新

class RegisterRequest(BaseModel):
    phone: str
    password: str
    username: Optional[str] = None

    @field_validator('phone')
    @classmethod
    def validate_phone(cls, v):
        if not re.match(r'^\d{11}$', v):
            raise ValueError('手机号必须是11位数字')
        return v

新增修改密码接口

class ChangePasswordRequest(BaseModel):
    old_password: str
    new_password: str

    @field_validator('new_password')
    @classmethod
    def validate_new_password(cls, v):
        if len(v) < 6:
            raise ValueError('新密码长度至少6位')
        return v

@router.post("/change-password")
async def change_password(request: ChangePasswordRequest, req: Request, response: Response):
    """修改密码,验证当前密码后更新"""
    # 1. 验证当前密码
    # 2. 更新密码 hash
    # 3. 重新生成 session token
    # 4. 返回新的 JWT Cookie

3. 配置更新

文件: backend/app/core/config.py

# 管理员配置
ADMIN_PHONE: str = ""  # 原 ADMIN_EMAIL
ADMIN_PASSWORD: str = ""

文件: backend/.env

ADMIN_PHONE=15549380526
ADMIN_PASSWORD=lam1988324

4. 管理员初始化 (main.py)

文件: backend/app/main.py

@app.on_event("startup")
async def init_admin():
    admin_phone = settings.ADMIN_PHONE  # 原 ADMIN_EMAIL
    # ... 使用 phone 字段创建管理员

5. 管理员 API (admin.py)

文件: backend/app/api/admin.py

class UserListItem(BaseModel):
    id: str
    phone: str  # 原 email
    username: Optional[str]
    role: str
    is_active: bool
    expires_at: Optional[str]
    created_at: str

🖥️ 前端修改

1. 登录页面 (login/page.tsx)

文件: frontend/src/app/login/page.tsx

const [phone, setPhone] = useState('');

// 验证手机号格式
if (!/^\d{11}$/.test(phone)) {
    setError('请输入正确的11位手机号');
    return;
}

<input
    type="tel"
    value={phone}
    onChange={(e) => setPhone(e.target.value.replace(/\D/g, '').slice(0, 11))}
    maxLength={11}
    placeholder="请输入11位手机号"
/>

2. 注册页面 (register/page.tsx)

同样使用手机号输入,增加 11 位数字验证。

3. Auth 工具函数 (auth.ts)

文件: frontend/src/lib/auth.ts

export interface User {
    id: string;
    phone: string;  // 原 email
    username: string | null;
    role: string;
    is_active: boolean;
}

export async function login(phone: string, password: string): Promise<AuthResponse> { ... }
export async function register(phone: string, password: string, username?: string): Promise<AuthResponse> { ... }
export async function changePassword(oldPassword: string, newPassword: string): Promise<AuthResponse> { ... }

4. 首页账户设置下拉菜单 (page.tsx)

文件: frontend/src/app/page.tsx

将原来的"退出"按钮改为账户设置下拉菜单:

function AccountSettingsDropdown() {
  const [isOpen, setIsOpen] = useState(false);
  const [showPasswordModal, setShowPasswordModal] = useState(false);
  // ...

  return (
    <div className="relative">
      <button onClick={() => setIsOpen(!isOpen)}>
        ⚙️ 账户
      </button>

      {/* 下拉菜单 */}
      {isOpen && (
        <div className="absolute right-0 mt-2 w-40 bg-gray-800 ...">
          <button onClick={() => setShowPasswordModal(true)}>
            🔐 修改密码
          </button>
          <button onClick={handleLogout} className="text-red-300">
            🚪 退出登录
          </button>
        </div>
      )}

      {/* 修改密码弹窗 */}
      {showPasswordModal && (
        <div className="fixed inset-0 z-50 ...">
          <form onSubmit={handleChangePassword}>
            <input placeholder="当前密码" />
            <input placeholder="新密码" />
            <input placeholder="确认新密码" />
          </form>
        </div>
      )}
    </div>
  );
}

5. 管理员页面 (admin/page.tsx)

文件: frontend/src/app/admin/page.tsx

interface UserListItem {
    id: string;
    phone: string;  // 原 email
    // ...
}

// 显示手机号而非邮箱
<div className="text-gray-400 text-sm">{user.phone}</div>

🗄️ 数据库迁移

迁移脚本

文件: backend/database/migrate_to_phone.sql

-- 删除旧表 (CASCADE 处理外键依赖)
DROP TABLE IF EXISTS user_sessions CASCADE;
DROP TABLE IF EXISTS social_accounts CASCADE;
DROP TABLE IF EXISTS users CASCADE;

-- 重新创建使用 phone 字段的表
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    phone TEXT UNIQUE NOT NULL,
    -- ...
);

-- 重新创建依赖表和索引
CREATE TABLE user_sessions (...);
CREATE TABLE social_accounts (...);
CREATE INDEX idx_users_phone ON users(phone);

执行方式

# 方式一Docker 命令
docker exec -i supabase-db psql -U postgres < backend/database/migrate_to_phone.sql

# 方式二Supabase Studio SQL Editor
# 打开 https://supabase.hbyrkj.top -> SQL Editor -> 粘贴执行

部署步骤

# 1. 执行数据库迁移
docker exec -i supabase-db psql -U postgres < backend/database/migrate_to_phone.sql

# 2. 重新构建前端
cd frontend && npm run build

# 3. 重启服务
pm2 restart vigent2-backend vigent2-frontend

📁 今日修改文件清单

文件 变更类型 说明
backend/database/schema.sql 修改 email → phone
backend/database/migrate_to_phone.sql 新增 数据库迁移脚本
backend/app/api/auth.py 修改 手机号验证 + 修改密码 API
backend/app/api/admin.py 修改 UserListItem.email → phone
backend/app/core/config.py 修改 ADMIN_EMAIL → ADMIN_PHONE
backend/app/main.py 修改 管理员初始化使用 phone
backend/.env 修改 ADMIN_PHONE=15549380526
frontend/src/app/login/page.tsx 修改 手机号登录 + 11位验证
frontend/src/app/register/page.tsx 修改 手机号注册 + 11位验证
frontend/src/lib/auth.ts 修改 phone 参数 + changePassword 函数
frontend/src/app/page.tsx 修改 AccountSettingsDropdown 组件
frontend/src/app/admin/page.tsx 修改 用户列表显示手机号
frontend/src/contexts/AuthContext.tsx 修改 存储完整用户信息含 expires_at

🆕 后续完善 (Day 15 下午)

账户有效期显示

在账户下拉菜单中显示用户的有效期:

显示情况 格式
有设置 expires_at 2026-03-15
NULL 永久有效

相关修改

  • backend/app/api/auth.py: UserResponse 新增 expires_at 字段
  • frontend/src/contexts/AuthContext.tsx: 存储完整用户对象
  • frontend/src/app/page.tsx: 格式化并显示有效期

点击外部关闭下拉菜单

使用 useRef + useEffect 监听全局点击事件,点击菜单外部自动关闭。

修改密码后强制重新登录

密码修改成功后:

  1. 显示"密码修改成功,正在跳转登录页..."
  2. 1.5秒后调用登出 API
  3. 跳转到登录页面

🔗 相关文档


🤖 模型与功能增强 (Day 15 晚)

1. GLM-4.7-Flash 升级

文件: backend/app/services/glm_service.py

将文案洗稿模型从 glm-4-flash 升级为 glm-4.7-flash

response = client.chat.completions.create(
    model="glm-4.7-flash",  # Upgrade from glm-4-flash
    messages=[...],
    # ...
)

改进:

  • 响应速度提升
  • 洗稿文案的流畅度和逻辑性增强

2. 独立文案提取助手

实现了独立的文案提取工具,支持从视频/音频文件或 URL 提取文字。

后端实现 (backend/app/api/tools.py)

  • 多源支持: 文件上传 (MP4/MP3/WAV) 或 URL 下载
  • 智能下载:
    • yt-dlp: 通用下载 (Douyin/TikTok/Bilibili)
    • Playwright: 智能回退机制 (Bilibili Dashboard API, Douyin Cookie Bypass)
  • URL 自动清洗: 正则提取分享文本中的 HTTP 链接
  • 流程: 下载 -> FFmpeg 转 WAV (16k) -> Whisper 识别 -> GLM-4.7 洗稿

前端实现 (frontend/src/components/ScriptExtractionModal.tsx)

  • 独立模态框: 通过顶部导航栏打开
  • 功能:
    • 链接粘贴 / 文件拖拽
    • 实时进度显示 (下载 -> 识别 -> 洗稿)
    • 一键填入: 将提取结果直接填充到主输入框
    • 自动识别: 自动区分平台与链接
  • 交互优化:
    • 防止误触背景关闭
    • 复制功能兼容 HTTP 环境 (Fallback textArea)

3. 上传视频预览功能

在素材列表 (frontend/src/app/page.tsx) 中为上传的视频添加预览功能:

  • 点击缩略图弹出视频播放模态框
  • 支持下载与发布快捷跳转

📝 任务清单更新

  • 认证系统迁移 (手机号)
  • 账户管理 (密码修改/有效期)
  • GLM-4.7 模型升级
  • 独立文案提取助手 (B站/抖音支持)
  • 视频预览功能