第一次提交

This commit is contained in:
2026-03-25 15:24:22 +08:00
commit 0f8ac68d4d
156 changed files with 42365 additions and 0 deletions

1
app/services/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Services package

View File

@@ -0,0 +1,256 @@
"""
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)}"

View File

@@ -0,0 +1,74 @@
"""
Product业务逻辑服务层
"""
from typing import Optional, Tuple, List
from flask import current_app
from sqlalchemy import func, or_
from app import db
from app.models import Product, Version
from app.utils.logger import log_operation
class ProductService:
"""产品业务逻辑服务"""
@staticmethod
def get_products(
page: int = 1,
per_page: int = 20,
keyword: str = '',
product_type: Optional[str] = None,
is_paid: Optional[int] = None
) -> Tuple[List[Product], int]:
"""
获取产品列表
:param page: 页码
:param per_page: 每页数量
:param keyword: 关键词
:param product_type: 产品类型
:param is_paid: 是否付费
:return: (产品列表, 总数)
"""
query = Product.query.filter_by(status=1) # 只显示启用的产品
# 关键词搜索
if keyword:
escaped_keyword = keyword.replace('%', '\\%').replace('_', '\\_')
pattern = f'%{escaped_keyword}%'
query = query.filter(
or_(
Product.product_name.like(pattern, escape='\\'),
Product.description.like(pattern, escape='\\')
)
)
# 筛选条件
if product_type:
query = query.filter(Product.product_type == product_type)
query = query.order_by(Product.create_time.desc())
# 分页
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
return pagination.items, pagination.total
@staticmethod
def get_product(product_id: str) -> Optional[Product]:
"""
获取产品详情
:param product_id: 产品ID
:return: 产品对象
"""
return Product.query.filter_by(product_id=product_id, status=1).first()
@staticmethod
def get_latest_version(product_id: str) -> Optional[Version]:
"""
获取产品最新版本
:param product_id: 产品ID
:return: 版本对象
"""
return Version.query.filter_by(
product_id=product_id,
publish_status=1
).order_by(Version.update_time.desc(), Version.create_time.desc()).first()