Kamixitong/app/services/license_service.py

257 lines
8.5 KiB
Python
Raw Normal View History

2025-12-12 11:35:14 +08:00
"""
License业务逻辑服务层
负责处理所有与License相关的业务逻辑将API层与模型层解耦
"""
from typing import Optional, Tuple, List
from flask import current_app
from sqlalchemy import func, or_
from app import db
from app.models import License, Product
from app.utils.logger import log_operation
import secrets
import string
class LicenseService:
"""卡密业务逻辑服务"""
@staticmethod
def generate_license_key(length: int = 32, prefix: str = '') -> str:
"""
生成卡密密钥
:param length: 密钥长度
:param prefix: 前缀
:return: 生成的密钥
"""
# 生成指定长度的随机字符串
chars = string.ascii_uppercase + string.digits
random_key = ''.join(secrets.choice(chars) for _ in range(length))
return f"{prefix}{random_key}" if prefix else random_key
@staticmethod
def create_license(
product_id: str,
valid_days: int,
license_type: int = 0,
max_bind_times: int = 1,
remark: str = ''
) -> Tuple[bool, str, Optional[License]]:
"""
创建卡密
:param product_id: 产品ID
:param valid_days: 有效天数
:param license_type: 卡密类型
:param max_bind_times: 最大绑定次数
:param remark: 备注
:return: (是否成功, 消息, 卡密对象)
"""
try:
# 检查产品是否存在
product = Product.query.filter_by(product_id=product_id, status=1).first()
if not product:
return False, "产品不存在或已下架", None
# 生成唯一的卡密
max_attempts = 10
for _ in range(max_attempts):
# 生成卡密
prefix = current_app.config.get('TRIAL_PREFIX', 'TRIAL_') if license_type == 1 else ''
length = current_app.config.get('LICENSE_KEY_LENGTH', 32)
license_key = LicenseService.generate_license_key(length, prefix)
# 检查是否已存在
existing = License.query.filter_by(license_key=license_key).first()
if not existing:
break
else:
return False, "生成卡密失败,请重试", None
# 创建卡密
license = License(
license_key=license_key,
product_id=product_id,
valid_days=valid_days,
type=license_type,
max_bind_times=max_bind_times,
remark=remark,
status=0 # 未激活
)
db.session.add(license)
db.session.commit()
return True, "卡密创建成功", license
except Exception as e:
db.session.rollback()
current_app.logger.error(f"创建卡密失败: {str(e)}")
return False, f"创建卡密失败: {str(e)}", None
@staticmethod
def batch_create_licenses(
product_id: str,
valid_days: int,
count: int,
license_type: int = 0,
max_bind_times: int = 1,
remark: str = ''
) -> Tuple[bool, str, List[License]]:
"""
批量创建卡密
:param product_id: 产品ID
:param valid_days: 有效天数
:param count: 数量
:param license_type: 卡密类型
:param max_bind_times: 最大绑定次数
:param remark: 备注
:return: (是否成功, 消息, 卡密列表)
"""
try:
licenses = []
for _ in range(count):
success, msg, license = LicenseService.create_license(
product_id, valid_days, license_type, max_bind_times, remark
)
if not success:
return False, f"创建失败: {msg}", []
licenses.append(license)
return True, f"成功创建{count}个卡密", licenses
except Exception as e:
db.session.rollback()
current_app.logger.error(f"批量创建卡密失败: {str(e)}")
return False, f"批量创建卡密失败: {str(e)}", []
@staticmethod
def get_licenses(
page: int = 1,
per_page: int = 20,
keyword: str = '',
license_type: Optional[int] = None,
status: Optional[int] = None,
product_id: str = ''
) -> Tuple[List[License], int]:
"""
获取卡密列表支持分页和筛选
:param page: 页码
:param per_page: 每页数量
:param keyword: 关键词
:param license_type: 卡密类型筛选
:param status: 状态筛选
:param product_id: 产品ID筛选
:return: (卡密列表, 总数)
"""
query = License.query.join(Product)
# 关键词搜索
if keyword:
# 转义特殊字符防止LIKE查询中的通配符攻击
escaped_keyword = keyword.replace('%', '\\%').replace('_', '\\_')
pattern = f'%{escaped_keyword.lower()}%'
query = query.filter(
or_(
func.lower(License.license_key).like(pattern, escape='\\'),
func.lower(Product.product_name).like(pattern, escape='\\')
)
)
# 筛选条件
if license_type is not None:
query = query.filter(License.type == license_type)
if status is not None:
query = query.filter(License.status == status)
if product_id:
query = query.filter(License.product_id == product_id)
# 排序
query = query.order_by(License.create_time.desc())
# 分页
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
return pagination.items, pagination.total
@staticmethod
def verify_license(license_key: str, machine_code: str, software_version: str) -> Tuple[bool, str, Optional[License]]:
"""
验证卡密
:param license_key: 卡密
:param machine_code: 机器码
:param software_version: 软件版本
:return: (是否成功, 消息, 卡密对象)
"""
try:
license = License.query.filter_by(license_key=license_key).first()
if not license:
return False, "卡密不存在", None
success, msg = license.verify(machine_code, software_version)
return success, msg, license if success else None
except Exception as e:
current_app.logger.error(f"验证卡密失败: {str(e)}")
return False, f"验证卡密失败: {str(e)}", None
@staticmethod
def unbind_license(license_key: str) -> Tuple[bool, str]:
"""
解绑卡密
:param license_key: 卡密
:return: (是否成功, 消息)
"""
try:
license = License.query.filter_by(license_key=license_key).first()
if not license:
return False, "卡密不存在"
success, msg = license.unbind()
return success, msg
except Exception as e:
db.session.rollback()
current_app.logger.error(f"解绑卡密失败: {str(e)}")
return False, f"解绑卡密失败: {str(e)}"
@staticmethod
def disable_license(license_key: str) -> Tuple[bool, str]:
"""
禁用卡密
:param license_key: 卡密
:return: (是否成功, 消息)
"""
try:
license = License.query.filter_by(license_key=license_key).first()
if not license:
return False, "卡密不存在"
success, msg = license.disable()
return success, msg
except Exception as e:
db.session.rollback()
current_app.logger.error(f"禁用卡密失败: {str(e)}")
return False, f"禁用卡密失败: {str(e)}"
@staticmethod
def delete_license(license_key: str) -> Tuple[bool, str]:
"""
删除卡密软删除
:param license_key: 卡密
:return: (是否成功, 消息)
"""
try:
license = License.query.filter_by(license_key=license_key).first()
if not license:
return False, "卡密不存在"
license.delete() # 假设有软删除方法
db.session.commit()
return True, "删除成功"
except Exception as e:
db.session.rollback()
current_app.logger.error(f"删除卡密失败: {str(e)}")
return False, f"删除卡密失败: {str(e)}"