Claude detection respects payment filter, add project skill

- Backend check-claude-payment accepts optional emails list
- Frontend sends filtered emails when filter is active
- Button label updates to show current filter scope
- Add project skill for development guidance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-06 14:10:06 +08:00
parent 4091bcc8ad
commit 18ae09af12
3 changed files with 217 additions and 4 deletions

View File

@@ -0,0 +1,168 @@
---
name: outlook-manager
description: |
Outlook 邮箱管理系统全能助手。涵盖项目开发、部署运维、数据库操作、前端 UI、API 接口等所有方面。
当用户在本项目中进行任何开发、修改、调试、部署、排错时都应使用此 skill。
触发场景包括但不限于添加功能、修改页面、数据库字段变更、Docker 部署、Redis 缓存、
邮件相关操作、Claude 支付检测、账号管理、前端表格/弹窗/筛选等。
---
# Outlook 邮箱管理系统 — 项目全能助手
## 项目概览
基于 FastAPI 的 Outlook 邮箱管理系统,通过 IMAP + OAuth2 读取邮件支持批量账号管理、Claude 支付状态检测、代理管理等功能。
**技术栈:** Python 3.12 / FastAPI / SQLite (WAL) / Redis / Docker / 原生 JS 前端
**Git 仓库:** `https://git.586vip.cn/huangzhenpc/claude-outlonok.git`
**服务器部署路径:** `/opt/claude-outlonok`
## 项目结构
```
outlookmanager_v2/
├── mail_api.py # 主应用入口,所有 API 路由FastAPI
├── config.py # 全局配置常量OAuth、IMAP、管理员令牌
├── database.py # SQLite 数据库管理器DatabaseManager 单例 db_manager
├── cache.py # Redis 缓存模块RedisCache 单例 cache
├── auth.py # OAuth2 令牌获取httpx 异步)
├── imap_client.py # IMAP 邮件客户端(按需连接,自动重试)
├── models.py # Pydantic 请求/响应模型
├── requirements.txt # Python 依赖
├── Dockerfile # Docker 镜像构建
├── docker-compose.yml # Docker 编排host 网络模式)
├── .env.example # 环境变量示例
├── data/
│ └── outlook_manager.db # SQLite 数据库git 跟踪,随项目迁移)
└── static/
├── index.html # 主页(账号列表 + 邮件查看器)
├── script.js # 主页 JSMailManager 类)
├── style.css # 全局样式
├── admin.html # 管理后台页面
└── admin.js # 管理后台 JS
```
## 数据库表结构SQLite
### accounts邮箱账号
| 字段 | 类型 | 说明 |
|------|------|------|
| email | TEXT PK | 邮箱地址 |
| password | TEXT | 密码 |
| client_id | TEXT | OAuth 客户端 ID |
| refresh_token | TEXT | OAuth 刷新令牌 |
| created_at | TIMESTAMP | 创建时间 |
### claude_payment_statusClaude 支付状态)
| 字段 | 类型 | 说明 |
|------|------|------|
| email | TEXT PK | 邮箱地址 |
| status | TEXT | 状态paid/refunded/suspended/error/unknown |
| payment_time | TEXT | 支付时间 |
| refund_time | TEXT | 退款时间 |
| suspended_time | TEXT | 封号时间 |
| refund_received | TEXT | 退款是否到账0/1 |
| refund_received_at | TEXT | 退款到账时间 |
| title | TEXT | 收据号 |
| remark | TEXT | 备注 |
| card_number | TEXT | 卡号后4位 |
| proxy | TEXT | 代理地址 |
| proxy_expire_days | INT | 代理有效天数 |
| proxy_share | TEXT | exclusive/shared |
| proxy_purchase_date | TEXT | 代理购买日期 |
| checked_at | TEXT | 最后检测时间 |
### account_tags账号标签
| 字段 | 类型 | 说明 |
|------|------|------|
| email | TEXT UNIQUE | 邮箱(外键关联 accounts级联删除 |
| tags | TEXT | JSON 数组字符串 |
### email_cache邮件缓存
| 字段 | 类型 | 说明 |
|------|------|------|
| email | TEXT | 邮箱 |
| message_id | TEXT | 邮件 ID |
| data | TEXT | JSON 邮件数据 |
### system_config系统配置
| 字段 | 类型 | 说明 |
|------|------|------|
| key | TEXT PK | 配置键 |
| value | TEXT | 配置值 |
## Redis 缓存策略
连接地址写死在 `cache.py``redis://:redis_XMiXNa@127.0.0.1:6379/0`
| 缓存键 | TTL | 说明 |
|--------|-----|------|
| cache:accounts | 5 分钟 | 全量账户列表 |
| cache:messages:{email}:{folder} | 3 分钟 | 邮件列表 |
| cache:payment_status | 10 分钟 | 支付状态 |
缓存失效时机:导入/删除账号、检测支付、更新备注/代理时自动清除对应缓存。
Redis 不可用时自动降级,不影响功能。
## 核心 API 路由
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/accounts/detailed | 分页账号列表(支持搜索、缓存) |
| GET | /api/messages | 邮件列表(支持 refresh=true 跳过缓存) |
| POST | /api/accounts/import | 文本格式批量导入账号 |
| DELETE | /api/account/{email} | 删除单个账号 |
| GET | /api/export | 导出账号为 txt |
| POST | /api/tools/check-claude-payment | SSE 流式批量检测 Claude 支付 |
| POST | /api/tools/check-claude-payment/{email} | 单个检测 |
| GET | /api/tools/claude-payment-status | 获取所有支付状态 |
| POST | /api/tools/refund-received/{email} | 切换退款到账状态 |
| POST | /api/tools/claude-payment-note/{email} | 更新备注/代理 |
## 前端架构
### 页面访问密码
前端登录密码写死在 `script.js``admin.js``oadmin123`,使用 `sessionStorage` 保持登录状态。
### 主页 MailManager 类script.js
- **账号视图**:表格展示,支持搜索、分页、支付状态筛选
- **邮件视图**:双栏布局(左侧列表 + 右侧详情 iframe
- **弹窗组件**:导入、凭证查看、备注编辑、代理编辑、确认对话框
- **筛选器**:全部/已支付/未支付/已退款/退款已到账/退款未到账/已封号/未检测
### 表格列顺序
`#` → 邮箱 → 密码 → 凭证 → 支付状态 → 退款状态 → 支付时间 → 退款时间 → 封号时间 → 到账时间 → 备注/卡号 → 代理 → 操作
### colspan
当前表格 colspan 值为 **13**。增减列时需同步更新 `index.html``script.js` 中所有 `colspan` 值。
## 开发规范
### 新增数据库字段的完整流程
1. `database.py` — CREATE TABLE 语句中添加字段
2. `database.py` — 兼容旧表的 ALTER TABLE 循环中添加字段和默认值
3. `database.py``allowed` 集合中添加字段(如果需要通过 `update_claude_payment_note` 更新)
4. `database.py` — INSERT OR REPLACE 语句中添加字段(保留旧值逻辑)
5. `database.py` — 所有 SELECT 查询和结果字典中添加字段
6. `mail_api.py` — 相关 API 端点处理新字段
7. `cache.py` — 数据变更时清除相关缓存
8. 前端 — 表头、渲染、colspan 同步更新
### 提交代码
- 提交到 `https://git.586vip.cn/huangzhenpc/claude-outlonok.git`
- 服务器更新命令:`cd /opt/claude-outlonok && git pull && docker compose up -d --build`
### Docker 部署
- 使用 `network_mode: host`,服务直接监听宿主机 **5001** 端口
- 外部访问端口 **5001**
- SQLite 数据库通过 `./data:/app/data` 挂载持久化
- 数据库文件 git 跟踪,随项目迁移
### 前端修改注意事项
- 增减表格列时必须同步更新 `colspan`index.html + script.js
- `renderClaudeColumns` 中 no-info 和 has-info 两个分支的 `<td>` 数量必须一致
- `updateClaudeBadgeInTable` 中的 cells 索引(当前从 index 5 开始,共 8 列)需匹配实际列数
- 操作列按钮通过 `renderRefundBtn` 动态渲染,仅已退款账号显示退款按钮

View File

@@ -1222,9 +1222,19 @@ async def _check_claude_payment_for_account(email_addr: str) -> dict:
}
@app.post("/api/tools/check-claude-payment")
async def check_claude_payment():
"""SSE流式扫描所有账户的Claude支付状态"""
async def check_claude_payment(request: Request):
"""SSE流式扫描账户的Claude支付状态,支持传入指定邮箱列表"""
try:
body = await request.json()
target_emails = body.get('emails', []) if body else []
except Exception:
target_emails = []
accounts = await load_accounts_config()
if target_emails:
# 只检测指定的邮箱(且必须在账户中存在)
emails = [e for e in target_emails if e in accounts]
else:
emails = list(accounts.keys())
async def event_generator():

View File

@@ -110,6 +110,7 @@ class MailManager {
this.paymentFilter = e.target.value;
this.page = 1;
this.loadAccounts();
this.updateClaudeBtnLabel();
});
// 刷新
@@ -1144,15 +1145,49 @@ class MailManager {
}
}
updateClaudeBtnLabel() {
const btn = document.getElementById('claudePaymentBtn');
if (!btn || btn.disabled) return;
const filterSelect = document.getElementById('paymentFilter');
const filterText = filterSelect.options[filterSelect.selectedIndex].text;
if (this.paymentFilter) {
btn.innerHTML = `<i class="bi bi-credit-card"></i><span>检测(${filterText})</span>`;
} else {
btn.innerHTML = '<i class="bi bi-credit-card"></i><span>Claude检测</span>';
}
}
async startClaudePaymentCheck() {
const btn = document.getElementById('claudePaymentBtn');
if (!btn) return;
// 根据筛选条件决定检测范围
let targetEmails = [];
if (this.paymentFilter && this._allAccounts) {
targetEmails = this._allAccounts
.filter(acc => this._matchPaymentFilter(acc.email))
.map(acc => acc.email);
}
const label = this.paymentFilter
? `检测 ${targetEmails.length}`
: '全部检测';
if (targetEmails.length === 0 && this.paymentFilter) {
this.showToast('当前筛选条件下没有账号', 'warning');
return;
}
btn.disabled = true;
const origHtml = btn.innerHTML;
btn.innerHTML = '<i class="bi bi-hourglass-split"></i><span>检测中...</span>';
try {
const response = await fetch('/api/tools/check-claude-payment', { method: 'POST' });
const response = await fetch('/api/tools/check-claude-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ emails: targetEmails })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';