蜂鸟Pro v2.0.1 - 基础框架版本 (待完善)
## 当前状态 - 插件界面已完成重命名 (cursorpro → hummingbird) - 双账号池 UI 已实现 (Auto/Pro 卡片) - 后端已切换到 MySQL 数据库 - 添加了 Cursor 官方用量 API 文档 ## 已知问题 (待修复) 1. 激活时检查账号导致无账号时激活失败 2. 未启用无感换号时不应获取账号 3. 账号用量模块不显示 (seamless 未启用时应隐藏) 4. 积分显示为 0 (后端未正确返回) 5. Auto/Pro 双密钥逻辑混乱,状态不同步 6. 账号添加后无自动分析功能 ## 下一版本计划 - 重构数据模型,优化账号状态管理 - 实现 Cursor API 自动分析账号 - 修复激活流程,不依赖账号 - 启用无感时才分配账号 - 完善账号用量实时显示 ## 文件说明 - docs/系统设计文档.md - 完整架构设计 - cursor 官方用量接口.md - Cursor API 文档 - 参考计费/ - Vibeviewer 开源项目参考 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -121,11 +121,17 @@ class KeyService:
|
||||
if retry == max_retries - 1:
|
||||
raise ValueError(f"无法生成唯一激活码,请重试")
|
||||
|
||||
# 根据类型设置默认值
|
||||
is_pro = key_data.membership_type == MembershipType.PRO
|
||||
db_key = ActivationKey(
|
||||
key=key_str,
|
||||
status=KeyStatus.UNUSED, # 新密钥默认未使用
|
||||
membership_type=key_data.membership_type,
|
||||
quota=key_data.quota if key_data.membership_type == MembershipType.PRO else 0, # Free不需要额度
|
||||
valid_days=key_data.valid_days,
|
||||
# 该密钥贡献的资源
|
||||
duration_days=key_data.valid_days if not is_pro else 0, # Auto贡献天数
|
||||
quota_contribution=key_data.quota if is_pro else 0, # Pro贡献积分
|
||||
# 主密钥初始值(激活时使用)
|
||||
quota=key_data.quota if is_pro else 0,
|
||||
max_devices=key_data.max_devices,
|
||||
remark=key_data.remark
|
||||
)
|
||||
@@ -171,22 +177,139 @@ class KeyService:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def activate(db: Session, key: ActivationKey):
|
||||
"""首次激活:设置激活时间和过期时间"""
|
||||
if key.first_activated_at is None:
|
||||
key.first_activated_at = datetime.now()
|
||||
if key.valid_days > 0:
|
||||
key.expire_at = key.first_activated_at + timedelta(days=key.valid_days)
|
||||
def activate(db: Session, key: ActivationKey, device_id: str = None) -> Tuple[bool, str, Optional[ActivationKey]]:
|
||||
"""
|
||||
激活密钥
|
||||
- 如果设备已有同类型主密钥,则合并(叠加时长/积分)
|
||||
- 否则,该密钥成为主密钥
|
||||
返回: (成功, 消息, 主密钥)
|
||||
"""
|
||||
now = datetime.now()
|
||||
|
||||
# 检查密钥状态
|
||||
if key.status == KeyStatus.MERGED:
|
||||
return False, "该密钥已被合并使用", None
|
||||
if key.status == KeyStatus.REVOKED:
|
||||
return False, "该密钥已被撤销", None
|
||||
if key.status == KeyStatus.DISABLED:
|
||||
return False, "该密钥已被禁用", None
|
||||
if key.status == KeyStatus.ACTIVE:
|
||||
# 已激活的密钥,检查是否是同设备
|
||||
if device_id and key.device_id and key.device_id != device_id:
|
||||
# 换设备激活,更新设备ID
|
||||
key.device_id = device_id
|
||||
db.commit()
|
||||
return True, "密钥已激活", key
|
||||
|
||||
# 查找该设备同类型的主密钥
|
||||
master_key = None
|
||||
if device_id:
|
||||
master_key = db.query(ActivationKey).filter(
|
||||
ActivationKey.device_id == device_id,
|
||||
ActivationKey.membership_type == key.membership_type,
|
||||
ActivationKey.status == KeyStatus.ACTIVE,
|
||||
ActivationKey.master_key_id == None # 是主密钥
|
||||
).first()
|
||||
|
||||
if master_key:
|
||||
# 合并到现有主密钥
|
||||
key.status = KeyStatus.MERGED
|
||||
key.master_key_id = master_key.id
|
||||
key.merged_at = now
|
||||
key.device_id = device_id
|
||||
|
||||
# 叠加资源到主密钥
|
||||
if key.membership_type == MembershipType.PRO:
|
||||
# Pro: 叠加积分
|
||||
master_key.quota += key.quota_contribution
|
||||
else:
|
||||
# Auto: 叠加时长
|
||||
if master_key.expire_at:
|
||||
master_key.expire_at += timedelta(days=key.duration_days)
|
||||
else:
|
||||
master_key.expire_at = now + timedelta(days=key.duration_days)
|
||||
|
||||
master_key.merged_count += 1
|
||||
db.commit()
|
||||
return True, f"密钥已合并,{'积分' if key.membership_type == MembershipType.PRO else '时长'}已叠加", master_key
|
||||
else:
|
||||
# 该密钥成为主密钥
|
||||
key.status = KeyStatus.ACTIVE
|
||||
key.device_id = device_id
|
||||
key.first_activated_at = now
|
||||
|
||||
# 设置初始到期时间(Auto)
|
||||
if key.membership_type == MembershipType.FREE and key.duration_days > 0:
|
||||
key.expire_at = now + timedelta(days=key.duration_days)
|
||||
|
||||
db.commit()
|
||||
return True, "激活成功", key
|
||||
|
||||
@staticmethod
|
||||
def get_master_key(db: Session, device_id: str, membership_type: MembershipType) -> Optional[ActivationKey]:
|
||||
"""获取设备的主密钥"""
|
||||
return db.query(ActivationKey).filter(
|
||||
ActivationKey.device_id == device_id,
|
||||
ActivationKey.membership_type == membership_type,
|
||||
ActivationKey.status == KeyStatus.ACTIVE,
|
||||
ActivationKey.master_key_id == None
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def get_device_keys(db: Session, device_id: str) -> dict:
|
||||
"""获取设备的所有密钥信息"""
|
||||
result = {"auto": None, "pro": None}
|
||||
|
||||
# 获取Auto主密钥
|
||||
auto_master = KeyService.get_master_key(db, device_id, MembershipType.FREE)
|
||||
if auto_master:
|
||||
# 获取合并的密钥
|
||||
merged_keys = db.query(ActivationKey).filter(
|
||||
ActivationKey.master_key_id == auto_master.id
|
||||
).all()
|
||||
result["auto"] = {
|
||||
"master": auto_master,
|
||||
"merged_keys": merged_keys,
|
||||
"total_keys": 1 + len(merged_keys),
|
||||
"expire_at": auto_master.expire_at
|
||||
}
|
||||
|
||||
# 获取Pro主密钥
|
||||
pro_master = KeyService.get_master_key(db, device_id, MembershipType.PRO)
|
||||
if pro_master:
|
||||
merged_keys = db.query(ActivationKey).filter(
|
||||
ActivationKey.master_key_id == pro_master.id
|
||||
).all()
|
||||
result["pro"] = {
|
||||
"master": pro_master,
|
||||
"merged_keys": merged_keys,
|
||||
"total_keys": 1 + len(merged_keys),
|
||||
"quota": pro_master.quota,
|
||||
"quota_used": pro_master.quota_used,
|
||||
"quota_remaining": pro_master.quota - pro_master.quota_used
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def is_valid(key: ActivationKey, db: Session) -> Tuple[bool, str]:
|
||||
"""检查激活码是否有效"""
|
||||
if key.status != KeyStatus.ACTIVE:
|
||||
"""检查激活码是否有效(仅检查主密钥)"""
|
||||
# 状态检查
|
||||
if key.status == KeyStatus.UNUSED:
|
||||
return False, "激活码未激活"
|
||||
if key.status == KeyStatus.MERGED:
|
||||
return False, "该密钥已合并,请使用主密钥"
|
||||
if key.status == KeyStatus.REVOKED:
|
||||
return False, "激活码已被撤销"
|
||||
if key.status == KeyStatus.DISABLED:
|
||||
return False, "激活码已禁用"
|
||||
if key.status == KeyStatus.EXPIRED:
|
||||
return False, "激活码已过期"
|
||||
if key.status != KeyStatus.ACTIVE:
|
||||
return False, "激活码状态异常"
|
||||
|
||||
# 检查是否已过期(只有激活后才检查)
|
||||
if key.first_activated_at and key.expire_at and key.expire_at < datetime.now():
|
||||
if key.expire_at and key.expire_at < datetime.now():
|
||||
return False, "激活码已过期"
|
||||
|
||||
# Pro套餐检查额度
|
||||
@@ -292,6 +415,66 @@ class KeyService:
|
||||
key.current_account_id = account.id
|
||||
db.commit()
|
||||
|
||||
@staticmethod
|
||||
def revoke_key(db: Session, key_id: int) -> Tuple[bool, str]:
|
||||
"""
|
||||
撤销密钥
|
||||
- 如果是主密钥:不允许直接撤销(需要先撤销所有合并的密钥)
|
||||
- 如果是合并的密钥:从主密钥扣除贡献的资源
|
||||
"""
|
||||
key = db.query(ActivationKey).filter(ActivationKey.id == key_id).first()
|
||||
if not key:
|
||||
return False, "密钥不存在"
|
||||
|
||||
if key.status == KeyStatus.REVOKED:
|
||||
return False, "密钥已被撤销"
|
||||
|
||||
if key.status == KeyStatus.ACTIVE and key.master_key_id is None:
|
||||
# 是主密钥,检查是否有合并的密钥
|
||||
merged_count = db.query(ActivationKey).filter(
|
||||
ActivationKey.master_key_id == key.id,
|
||||
ActivationKey.status == KeyStatus.MERGED
|
||||
).count()
|
||||
if merged_count > 0:
|
||||
return False, f"该密钥有{merged_count}个合并密钥,请先撤销合并的密钥"
|
||||
|
||||
# 主密钥没有合并密钥,可以直接撤销
|
||||
key.status = KeyStatus.REVOKED
|
||||
db.commit()
|
||||
return True, "主密钥已撤销"
|
||||
|
||||
elif key.status == KeyStatus.MERGED:
|
||||
# 是合并的密钥,从主密钥扣除资源
|
||||
master = db.query(ActivationKey).filter(ActivationKey.id == key.master_key_id).first()
|
||||
if not master:
|
||||
return False, "找不到主密钥"
|
||||
|
||||
if key.membership_type == MembershipType.PRO:
|
||||
# Pro: 检查扣除后是否会导致已用超额
|
||||
new_quota = master.quota - key.quota_contribution
|
||||
if master.quota_used > new_quota:
|
||||
return False, f"无法撤销:撤销后剩余额度({new_quota})小于已用额度({master.quota_used})"
|
||||
master.quota = new_quota
|
||||
else:
|
||||
# Auto: 扣除时长
|
||||
if master.expire_at:
|
||||
master.expire_at -= timedelta(days=key.duration_days)
|
||||
# 检查扣除后是否已过期
|
||||
if master.expire_at < datetime.now():
|
||||
return False, "无法撤销:撤销后密钥将立即过期"
|
||||
|
||||
master.merged_count -= 1
|
||||
key.status = KeyStatus.REVOKED
|
||||
key.master_key_id = None # 解除关联
|
||||
db.commit()
|
||||
return True, "合并密钥已撤销,资源已扣除"
|
||||
|
||||
else:
|
||||
# 其他状态(UNUSED, DISABLED 等)
|
||||
key.status = KeyStatus.REVOKED
|
||||
db.commit()
|
||||
return True, "密钥已撤销"
|
||||
|
||||
@staticmethod
|
||||
def count(db: Session) -> dict:
|
||||
"""统计激活码数量"""
|
||||
|
||||
Reference in New Issue
Block a user