Kamixitong/app/services/license_service.py
2025-12-12 11:35:14 +08:00

257 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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)}"