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:
168
.claude/skills/outlook-manager/SKILL.md
Normal file
168
.claude/skills/outlook-manager/SKILL.md
Normal 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 # 主页 JS(MailManager 类)
|
||||
├── 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_status(Claude 支付状态)
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| 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` 动态渲染,仅已退款账号显示退款按钮
|
||||
16
mail_api.py
16
mail_api.py
@@ -1222,10 +1222,20 @@ 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()
|
||||
emails = list(accounts.keys())
|
||||
if target_emails:
|
||||
# 只检测指定的邮箱(且必须在账户中存在)
|
||||
emails = [e for e in target_emails if e in accounts]
|
||||
else:
|
||||
emails = list(accounts.keys())
|
||||
|
||||
async def event_generator():
|
||||
total = len(emails)
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
Reference in New Issue
Block a user