257 lines
8.5 KiB
Python
257 lines
8.5 KiB
Python
|
|
"""
|
|||
|
|
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)}"
|